Updated Ukrainian translations
[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, startingEngine = 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     keepInfo = 1;
871     if(oldMode != BeginningOfGame) EditGameEvent();
872     keepInfo = 0;
873     UnloadEngine(cps);
874     appData.noChessProgram = FALSE;
875     appData.clockMode = TRUE;
876     InitEngine(cps, n);
877     UpdateLogos(TRUE);
878     if(n) return; // only startup first engine immediately; second can wait
879     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
880     LoadEngine();
881 }
882
883 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
884 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
885
886 static char resetOptions[] =
887         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
888         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
889         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
890         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
891
892 void
893 FloatToFront(char **list, char *engineLine)
894 {
895     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
896     int i=0;
897     if(appData.recentEngines <= 0) return;
898     TidyProgramName(engineLine, "localhost", tidy+1);
899     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
900     strncpy(buf+1, *list, MSG_SIZ-50);
901     if(p = strstr(buf, tidy)) { // tidy name appears in list
902         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
903         while(*p++ = *++q); // squeeze out
904     }
905     strcat(tidy, buf+1); // put list behind tidy name
906     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
907     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
908     ASSIGN(*list, tidy+1);
909 }
910
911 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
912
913 void
914 Load (ChessProgramState *cps, int i)
915 {
916     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
917     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
918         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
919         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
920         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
921         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
922         appData.firstProtocolVersion = PROTOVER;
923         ParseArgsFromString(buf);
924         SwapEngines(i);
925         ReplaceEngine(cps, i);
926         FloatToFront(&appData.recentEngineList, engineLine);
927         return;
928     }
929     p = engineName;
930     while(q = strchr(p, SLASH)) p = q+1;
931     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
932     if(engineDir[0] != NULLCHAR) {
933         ASSIGN(appData.directory[i], engineDir); p = engineName;
934     } else if(p != engineName) { // derive directory from engine path, when not given
935         p[-1] = 0;
936         ASSIGN(appData.directory[i], engineName);
937         p[-1] = SLASH;
938         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
939     } else { ASSIGN(appData.directory[i], "."); }
940     if(params[0]) {
941         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
942         snprintf(command, MSG_SIZ, "%s %s", p, params);
943         p = command;
944     }
945     ASSIGN(appData.chessProgram[i], p);
946     appData.isUCI[i] = isUCI;
947     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
948     appData.hasOwnBookUCI[i] = hasBook;
949     if(!nickName[0]) useNick = FALSE;
950     if(useNick) ASSIGN(appData.pgnName[i], nickName);
951     if(addToList) {
952         int len;
953         char quote;
954         q = firstChessProgramNames;
955         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
956         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
957         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
958                         quote, p, quote, appData.directory[i],
959                         useNick ? " -fn \"" : "",
960                         useNick ? nickName : "",
961                         useNick ? "\"" : "",
962                         v1 ? " -firstProtocolVersion 1" : "",
963                         hasBook ? "" : " -fNoOwnBookUCI",
964                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
965                         storeVariant ? " -variant " : "",
966                         storeVariant ? VariantName(gameInfo.variant) : "");
967         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
968         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
969         if(insert != q) insert[-1] = NULLCHAR;
970         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
971         if(q)   free(q);
972         FloatToFront(&appData.recentEngineList, buf);
973     }
974     ReplaceEngine(cps, i);
975 }
976
977 void
978 InitTimeControls ()
979 {
980     int matched, min, sec;
981     /*
982      * Parse timeControl resource
983      */
984     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
985                           appData.movesPerSession)) {
986         char buf[MSG_SIZ];
987         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
988         DisplayFatalError(buf, 0, 2);
989     }
990
991     /*
992      * Parse searchTime resource
993      */
994     if (*appData.searchTime != NULLCHAR) {
995         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
996         if (matched == 1) {
997             searchTime = min * 60;
998         } else if (matched == 2) {
999             searchTime = min * 60 + sec;
1000         } else {
1001             char buf[MSG_SIZ];
1002             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1003             DisplayFatalError(buf, 0, 2);
1004         }
1005     }
1006 }
1007
1008 void
1009 InitBackEnd1 ()
1010 {
1011
1012     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1013     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1014
1015     GetTimeMark(&programStartTime);
1016     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1017     appData.seedBase = random() + (random()<<15);
1018     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1019
1020     ClearProgramStats();
1021     programStats.ok_to_send = 1;
1022     programStats.seen_stat = 0;
1023
1024     /*
1025      * Initialize game list
1026      */
1027     ListNew(&gameList);
1028
1029
1030     /*
1031      * Internet chess server status
1032      */
1033     if (appData.icsActive) {
1034         appData.matchMode = FALSE;
1035         appData.matchGames = 0;
1036 #if ZIPPY
1037         appData.noChessProgram = !appData.zippyPlay;
1038 #else
1039         appData.zippyPlay = FALSE;
1040         appData.zippyTalk = FALSE;
1041         appData.noChessProgram = TRUE;
1042 #endif
1043         if (*appData.icsHelper != NULLCHAR) {
1044             appData.useTelnet = TRUE;
1045             appData.telnetProgram = appData.icsHelper;
1046         }
1047     } else {
1048         appData.zippyTalk = appData.zippyPlay = FALSE;
1049     }
1050
1051     /* [AS] Initialize pv info list [HGM] and game state */
1052     {
1053         int i, j;
1054
1055         for( i=0; i<=framePtr; i++ ) {
1056             pvInfoList[i].depth = -1;
1057             boards[i][EP_STATUS] = EP_NONE;
1058             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1059         }
1060     }
1061
1062     InitTimeControls();
1063
1064     /* [AS] Adjudication threshold */
1065     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1066
1067     InitEngine(&first, 0);
1068     InitEngine(&second, 1);
1069     CommonEngineInit();
1070
1071     pairing.which = "pairing"; // pairing engine
1072     pairing.pr = NoProc;
1073     pairing.isr = NULL;
1074     pairing.program = appData.pairingEngine;
1075     pairing.host = "localhost";
1076     pairing.dir = ".";
1077
1078     if (appData.icsActive) {
1079         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1080     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1081         appData.clockMode = FALSE;
1082         first.sendTime = second.sendTime = 0;
1083     }
1084
1085 #if ZIPPY
1086     /* Override some settings from environment variables, for backward
1087        compatibility.  Unfortunately it's not feasible to have the env
1088        vars just set defaults, at least in xboard.  Ugh.
1089     */
1090     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1091       ZippyInit();
1092     }
1093 #endif
1094
1095     if (!appData.icsActive) {
1096       char buf[MSG_SIZ];
1097       int len;
1098
1099       /* Check for variants that are supported only in ICS mode,
1100          or not at all.  Some that are accepted here nevertheless
1101          have bugs; see comments below.
1102       */
1103       VariantClass variant = StringToVariant(appData.variant);
1104       switch (variant) {
1105       case VariantBughouse:     /* need four players and two boards */
1106       case VariantKriegspiel:   /* need to hide pieces and move details */
1107         /* case VariantFischeRandom: (Fabien: moved below) */
1108         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantUnknown:
1116       case VariantLoadable:
1117       case Variant29:
1118       case Variant30:
1119       case Variant31:
1120       case Variant32:
1121       case Variant33:
1122       case Variant34:
1123       case Variant35:
1124       case Variant36:
1125       default:
1126         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1127         if( (len >= MSG_SIZ) && appData.debugMode )
1128           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1129
1130         DisplayFatalError(buf, 0, 2);
1131         return;
1132
1133       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1134       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1135       case VariantGothic:     /* [HGM] should work */
1136       case VariantCapablanca: /* [HGM] should work */
1137       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1138       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1139       case VariantKnightmate: /* [HGM] should work */
1140       case VariantCylinder:   /* [HGM] untested */
1141       case VariantFalcon:     /* [HGM] untested */
1142       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1143                                  offboard interposition not understood */
1144       case VariantNormal:     /* definitely works! */
1145       case VariantWildCastle: /* pieces not automatically shuffled */
1146       case VariantNoCastle:   /* pieces not automatically shuffled */
1147       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1148       case VariantLosers:     /* should work except for win condition,
1149                                  and doesn't know captures are mandatory */
1150       case VariantSuicide:    /* should work except for win condition,
1151                                  and doesn't know captures are mandatory */
1152       case VariantGiveaway:   /* should work except for win condition,
1153                                  and doesn't know captures are mandatory */
1154       case VariantTwoKings:   /* should work */
1155       case VariantAtomic:     /* should work except for win condition */
1156       case Variant3Check:     /* should work except for win condition */
1157       case VariantShatranj:   /* should work except for all win conditions */
1158       case VariantMakruk:     /* should work except for draw countdown */
1159       case VariantBerolina:   /* might work if TestLegality is off */
1160       case VariantCapaRandom: /* should work */
1161       case VariantJanus:      /* should work */
1162       case VariantSuper:      /* experimental */
1163       case VariantGreat:      /* experimental, requires legality testing to be off */
1164       case VariantSChess:     /* S-Chess, should work */
1165       case VariantGrand:      /* should work */
1166       case VariantSpartan:    /* should work */
1167         break;
1168       }
1169     }
1170
1171 }
1172
1173 int
1174 NextIntegerFromString (char ** str, long * value)
1175 {
1176     int result = -1;
1177     char * s = *str;
1178
1179     while( *s == ' ' || *s == '\t' ) {
1180         s++;
1181     }
1182
1183     *value = 0;
1184
1185     if( *s >= '0' && *s <= '9' ) {
1186         while( *s >= '0' && *s <= '9' ) {
1187             *value = *value * 10 + (*s - '0');
1188             s++;
1189         }
1190
1191         result = 0;
1192     }
1193
1194     *str = s;
1195
1196     return result;
1197 }
1198
1199 int
1200 NextTimeControlFromString (char ** str, long * value)
1201 {
1202     long temp;
1203     int result = NextIntegerFromString( str, &temp );
1204
1205     if( result == 0 ) {
1206         *value = temp * 60; /* Minutes */
1207         if( **str == ':' ) {
1208             (*str)++;
1209             result = NextIntegerFromString( str, &temp );
1210             *value += temp; /* Seconds */
1211         }
1212     }
1213
1214     return result;
1215 }
1216
1217 int
1218 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1219 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1220     int result = -1, type = 0; long temp, temp2;
1221
1222     if(**str != ':') return -1; // old params remain in force!
1223     (*str)++;
1224     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1225     if( NextIntegerFromString( str, &temp ) ) return -1;
1226     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1227
1228     if(**str != '/') {
1229         /* time only: incremental or sudden-death time control */
1230         if(**str == '+') { /* increment follows; read it */
1231             (*str)++;
1232             if(**str == '!') type = *(*str)++; // Bronstein TC
1233             if(result = NextIntegerFromString( str, &temp2)) return -1;
1234             *inc = temp2 * 1000;
1235             if(**str == '.') { // read fraction of increment
1236                 char *start = ++(*str);
1237                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1238                 temp2 *= 1000;
1239                 while(start++ < *str) temp2 /= 10;
1240                 *inc += temp2;
1241             }
1242         } else *inc = 0;
1243         *moves = 0; *tc = temp * 1000; *incType = type;
1244         return 0;
1245     }
1246
1247     (*str)++; /* classical time control */
1248     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1249
1250     if(result == 0) {
1251         *moves = temp;
1252         *tc    = temp2 * 1000;
1253         *inc   = 0;
1254         *incType = type;
1255     }
1256     return result;
1257 }
1258
1259 int
1260 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1261 {   /* [HGM] get time to add from the multi-session time-control string */
1262     int incType, moves=1; /* kludge to force reading of first session */
1263     long time, increment;
1264     char *s = tcString;
1265
1266     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1267     do {
1268         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1269         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1270         if(movenr == -1) return time;    /* last move before new session     */
1271         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1272         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1273         if(!moves) return increment;     /* current session is incremental   */
1274         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1275     } while(movenr >= -1);               /* try again for next session       */
1276
1277     return 0; // no new time quota on this move
1278 }
1279
1280 int
1281 ParseTimeControl (char *tc, float ti, int mps)
1282 {
1283   long tc1;
1284   long tc2;
1285   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1286   int min, sec=0;
1287
1288   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1289   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1290       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1291   if(ti > 0) {
1292
1293     if(mps)
1294       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1295     else
1296       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1297   } else {
1298     if(mps)
1299       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1300     else
1301       snprintf(buf, MSG_SIZ, ":%s", mytc);
1302   }
1303   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1304
1305   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1306     return FALSE;
1307   }
1308
1309   if( *tc == '/' ) {
1310     /* Parse second time control */
1311     tc++;
1312
1313     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1314       return FALSE;
1315     }
1316
1317     if( tc2 == 0 ) {
1318       return FALSE;
1319     }
1320
1321     timeControl_2 = tc2 * 1000;
1322   }
1323   else {
1324     timeControl_2 = 0;
1325   }
1326
1327   if( tc1 == 0 ) {
1328     return FALSE;
1329   }
1330
1331   timeControl = tc1 * 1000;
1332
1333   if (ti >= 0) {
1334     timeIncrement = ti * 1000;  /* convert to ms */
1335     movesPerSession = 0;
1336   } else {
1337     timeIncrement = 0;
1338     movesPerSession = mps;
1339   }
1340   return TRUE;
1341 }
1342
1343 void
1344 InitBackEnd2 ()
1345 {
1346     if (appData.debugMode) {
1347 #    ifdef __GIT_VERSION
1348       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1349 #    else
1350       fprintf(debugFP, "Version: %s\n", programVersion);
1351 #    endif
1352     }
1353     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1354
1355     set_cont_sequence(appData.wrapContSeq);
1356     if (appData.matchGames > 0) {
1357         appData.matchMode = TRUE;
1358     } else if (appData.matchMode) {
1359         appData.matchGames = 1;
1360     }
1361     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1362         appData.matchGames = appData.sameColorGames;
1363     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1364         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1365         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1366     }
1367     Reset(TRUE, FALSE);
1368     if (appData.noChessProgram || first.protocolVersion == 1) {
1369       InitBackEnd3();
1370     } else {
1371       /* kludge: allow timeout for initial "feature" commands */
1372       FreezeUI();
1373       DisplayMessage("", _("Starting chess program"));
1374       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1375     }
1376 }
1377
1378 int
1379 CalculateIndex (int index, int gameNr)
1380 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1381     int res;
1382     if(index > 0) return index; // fixed nmber
1383     if(index == 0) return 1;
1384     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1385     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1386     return res;
1387 }
1388
1389 int
1390 LoadGameOrPosition (int gameNr)
1391 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1392     if (*appData.loadGameFile != NULLCHAR) {
1393         if (!LoadGameFromFile(appData.loadGameFile,
1394                 CalculateIndex(appData.loadGameIndex, gameNr),
1395                               appData.loadGameFile, FALSE)) {
1396             DisplayFatalError(_("Bad game file"), 0, 1);
1397             return 0;
1398         }
1399     } else if (*appData.loadPositionFile != NULLCHAR) {
1400         if (!LoadPositionFromFile(appData.loadPositionFile,
1401                 CalculateIndex(appData.loadPositionIndex, gameNr),
1402                                   appData.loadPositionFile)) {
1403             DisplayFatalError(_("Bad position file"), 0, 1);
1404             return 0;
1405         }
1406     }
1407     return 1;
1408 }
1409
1410 void
1411 ReserveGame (int gameNr, char resChar)
1412 {
1413     FILE *tf = fopen(appData.tourneyFile, "r+");
1414     char *p, *q, c, buf[MSG_SIZ];
1415     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1416     safeStrCpy(buf, lastMsg, MSG_SIZ);
1417     DisplayMessage(_("Pick new game"), "");
1418     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1419     ParseArgsFromFile(tf);
1420     p = q = appData.results;
1421     if(appData.debugMode) {
1422       char *r = appData.participants;
1423       fprintf(debugFP, "results = '%s'\n", p);
1424       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1425       fprintf(debugFP, "\n");
1426     }
1427     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1428     nextGame = q - p;
1429     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1430     safeStrCpy(q, p, strlen(p) + 2);
1431     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1432     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1433     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1434         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1435         q[nextGame] = '*';
1436     }
1437     fseek(tf, -(strlen(p)+4), SEEK_END);
1438     c = fgetc(tf);
1439     if(c != '"') // depending on DOS or Unix line endings we can be one off
1440          fseek(tf, -(strlen(p)+2), SEEK_END);
1441     else fseek(tf, -(strlen(p)+3), SEEK_END);
1442     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1443     DisplayMessage(buf, "");
1444     free(p); appData.results = q;
1445     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1446        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1447       int round = appData.defaultMatchGames * appData.tourneyType;
1448       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1449          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1450         UnloadEngine(&first);  // next game belongs to other pairing;
1451         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1452     }
1453     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1454 }
1455
1456 void
1457 MatchEvent (int mode)
1458 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1459         int dummy;
1460         if(matchMode) { // already in match mode: switch it off
1461             abortMatch = TRUE;
1462             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1463             return;
1464         }
1465 //      if(gameMode != BeginningOfGame) {
1466 //          DisplayError(_("You can only start a match from the initial position."), 0);
1467 //          return;
1468 //      }
1469         abortMatch = FALSE;
1470         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1471         /* Set up machine vs. machine match */
1472         nextGame = 0;
1473         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1474         if(appData.tourneyFile[0]) {
1475             ReserveGame(-1, 0);
1476             if(nextGame > appData.matchGames) {
1477                 char buf[MSG_SIZ];
1478                 if(strchr(appData.results, '*') == NULL) {
1479                     FILE *f;
1480                     appData.tourneyCycles++;
1481                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1482                         fclose(f);
1483                         NextTourneyGame(-1, &dummy);
1484                         ReserveGame(-1, 0);
1485                         if(nextGame <= appData.matchGames) {
1486                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1487                             matchMode = mode;
1488                             ScheduleDelayedEvent(NextMatchGame, 10000);
1489                             return;
1490                         }
1491                     }
1492                 }
1493                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1494                 DisplayError(buf, 0);
1495                 appData.tourneyFile[0] = 0;
1496                 return;
1497             }
1498         } else
1499         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1500             DisplayFatalError(_("Can't have a match with no chess programs"),
1501                               0, 2);
1502             return;
1503         }
1504         matchMode = mode;
1505         matchGame = roundNr = 1;
1506         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1507         NextMatchGame();
1508 }
1509
1510 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1511
1512 void
1513 InitBackEnd3 P((void))
1514 {
1515     GameMode initialMode;
1516     char buf[MSG_SIZ];
1517     int err, len;
1518
1519     InitChessProgram(&first, startedFromSetupPosition);
1520
1521     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1522         free(programVersion);
1523         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1524         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1525         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1526     }
1527
1528     if (appData.icsActive) {
1529 #ifdef WIN32
1530         /* [DM] Make a console window if needed [HGM] merged ifs */
1531         ConsoleCreate();
1532 #endif
1533         err = establish();
1534         if (err != 0)
1535           {
1536             if (*appData.icsCommPort != NULLCHAR)
1537               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1538                              appData.icsCommPort);
1539             else
1540               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1541                         appData.icsHost, appData.icsPort);
1542
1543             if( (len >= MSG_SIZ) && appData.debugMode )
1544               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1545
1546             DisplayFatalError(buf, err, 1);
1547             return;
1548         }
1549         SetICSMode();
1550         telnetISR =
1551           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1552         fromUserISR =
1553           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1554         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1555             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1556     } else if (appData.noChessProgram) {
1557         SetNCPMode();
1558     } else {
1559         SetGNUMode();
1560     }
1561
1562     if (*appData.cmailGameName != NULLCHAR) {
1563         SetCmailMode();
1564         OpenLoopback(&cmailPR);
1565         cmailISR =
1566           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1567     }
1568
1569     ThawUI();
1570     DisplayMessage("", "");
1571     if (StrCaseCmp(appData.initialMode, "") == 0) {
1572       initialMode = BeginningOfGame;
1573       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1574         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1575         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1576         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1577         ModeHighlight();
1578       }
1579     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1580       initialMode = TwoMachinesPlay;
1581     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1582       initialMode = AnalyzeFile;
1583     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1584       initialMode = AnalyzeMode;
1585     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1586       initialMode = MachinePlaysWhite;
1587     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1588       initialMode = MachinePlaysBlack;
1589     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1590       initialMode = EditGame;
1591     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1592       initialMode = EditPosition;
1593     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1594       initialMode = Training;
1595     } else {
1596       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1597       if( (len >= MSG_SIZ) && appData.debugMode )
1598         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1599
1600       DisplayFatalError(buf, 0, 2);
1601       return;
1602     }
1603
1604     if (appData.matchMode) {
1605         if(appData.tourneyFile[0]) { // start tourney from command line
1606             FILE *f;
1607             if(f = fopen(appData.tourneyFile, "r")) {
1608                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1609                 fclose(f);
1610                 appData.clockMode = TRUE;
1611                 SetGNUMode();
1612             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1613         }
1614         MatchEvent(TRUE);
1615     } else if (*appData.cmailGameName != NULLCHAR) {
1616         /* Set up cmail mode */
1617         ReloadCmailMsgEvent(TRUE);
1618     } else {
1619         /* Set up other modes */
1620         if (initialMode == AnalyzeFile) {
1621           if (*appData.loadGameFile == NULLCHAR) {
1622             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1623             return;
1624           }
1625         }
1626         if (*appData.loadGameFile != NULLCHAR) {
1627             (void) LoadGameFromFile(appData.loadGameFile,
1628                                     appData.loadGameIndex,
1629                                     appData.loadGameFile, TRUE);
1630         } else if (*appData.loadPositionFile != NULLCHAR) {
1631             (void) LoadPositionFromFile(appData.loadPositionFile,
1632                                         appData.loadPositionIndex,
1633                                         appData.loadPositionFile);
1634             /* [HGM] try to make self-starting even after FEN load */
1635             /* to allow automatic setup of fairy variants with wtm */
1636             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1637                 gameMode = BeginningOfGame;
1638                 setboardSpoiledMachineBlack = 1;
1639             }
1640             /* [HGM] loadPos: make that every new game uses the setup */
1641             /* from file as long as we do not switch variant          */
1642             if(!blackPlaysFirst) {
1643                 startedFromPositionFile = TRUE;
1644                 CopyBoard(filePosition, boards[0]);
1645             }
1646         }
1647         if (initialMode == AnalyzeMode) {
1648           if (appData.noChessProgram) {
1649             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1650             return;
1651           }
1652           if (appData.icsActive) {
1653             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1654             return;
1655           }
1656           AnalyzeModeEvent();
1657         } else if (initialMode == AnalyzeFile) {
1658           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1659           ShowThinkingEvent();
1660           AnalyzeFileEvent();
1661           AnalysisPeriodicEvent(1);
1662         } else if (initialMode == MachinePlaysWhite) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           MachineWhiteEvent();
1674         } else if (initialMode == MachinePlaysBlack) {
1675           if (appData.noChessProgram) {
1676             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1677                               0, 2);
1678             return;
1679           }
1680           if (appData.icsActive) {
1681             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1682                               0, 2);
1683             return;
1684           }
1685           MachineBlackEvent();
1686         } else if (initialMode == TwoMachinesPlay) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1689                               0, 2);
1690             return;
1691           }
1692           if (appData.icsActive) {
1693             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1694                               0, 2);
1695             return;
1696           }
1697           TwoMachinesEvent();
1698         } else if (initialMode == EditGame) {
1699           EditGameEvent();
1700         } else if (initialMode == EditPosition) {
1701           EditPositionEvent();
1702         } else if (initialMode == Training) {
1703           if (*appData.loadGameFile == NULLCHAR) {
1704             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1705             return;
1706           }
1707           TrainingEvent();
1708         }
1709     }
1710 }
1711
1712 void
1713 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1714 {
1715     DisplayBook(current+1);
1716
1717     MoveHistorySet( movelist, first, last, current, pvInfoList );
1718
1719     EvalGraphSet( first, last, current, pvInfoList );
1720
1721     MakeEngineOutputTitle();
1722 }
1723
1724 /*
1725  * Establish will establish a contact to a remote host.port.
1726  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1727  *  used to talk to the host.
1728  * Returns 0 if okay, error code if not.
1729  */
1730 int
1731 establish ()
1732 {
1733     char buf[MSG_SIZ];
1734
1735     if (*appData.icsCommPort != NULLCHAR) {
1736         /* Talk to the host through a serial comm port */
1737         return OpenCommPort(appData.icsCommPort, &icsPR);
1738
1739     } else if (*appData.gateway != NULLCHAR) {
1740         if (*appData.remoteShell == NULLCHAR) {
1741             /* Use the rcmd protocol to run telnet program on a gateway host */
1742             snprintf(buf, sizeof(buf), "%s %s %s",
1743                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1744             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1745
1746         } else {
1747             /* Use the rsh program to run telnet program on a gateway host */
1748             if (*appData.remoteUser == NULLCHAR) {
1749                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1750                         appData.gateway, appData.telnetProgram,
1751                         appData.icsHost, appData.icsPort);
1752             } else {
1753                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1754                         appData.remoteShell, appData.gateway,
1755                         appData.remoteUser, appData.telnetProgram,
1756                         appData.icsHost, appData.icsPort);
1757             }
1758             return StartChildProcess(buf, "", &icsPR);
1759
1760         }
1761     } else if (appData.useTelnet) {
1762         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1763
1764     } else {
1765         /* TCP socket interface differs somewhat between
1766            Unix and NT; handle details in the front end.
1767            */
1768         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1769     }
1770 }
1771
1772 void
1773 EscapeExpand (char *p, char *q)
1774 {       // [HGM] initstring: routine to shape up string arguments
1775         while(*p++ = *q++) if(p[-1] == '\\')
1776             switch(*q++) {
1777                 case 'n': p[-1] = '\n'; break;
1778                 case 'r': p[-1] = '\r'; break;
1779                 case 't': p[-1] = '\t'; break;
1780                 case '\\': p[-1] = '\\'; break;
1781                 case 0: *p = 0; return;
1782                 default: p[-1] = q[-1]; break;
1783             }
1784 }
1785
1786 void
1787 show_bytes (FILE *fp, char *buf, int count)
1788 {
1789     while (count--) {
1790         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1791             fprintf(fp, "\\%03o", *buf & 0xff);
1792         } else {
1793             putc(*buf, fp);
1794         }
1795         buf++;
1796     }
1797     fflush(fp);
1798 }
1799
1800 /* Returns an errno value */
1801 int
1802 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1803 {
1804     char buf[8192], *p, *q, *buflim;
1805     int left, newcount, outcount;
1806
1807     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1808         *appData.gateway != NULLCHAR) {
1809         if (appData.debugMode) {
1810             fprintf(debugFP, ">ICS: ");
1811             show_bytes(debugFP, message, count);
1812             fprintf(debugFP, "\n");
1813         }
1814         return OutputToProcess(pr, message, count, outError);
1815     }
1816
1817     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1818     p = message;
1819     q = buf;
1820     left = count;
1821     newcount = 0;
1822     while (left) {
1823         if (q >= buflim) {
1824             if (appData.debugMode) {
1825                 fprintf(debugFP, ">ICS: ");
1826                 show_bytes(debugFP, buf, newcount);
1827                 fprintf(debugFP, "\n");
1828             }
1829             outcount = OutputToProcess(pr, buf, newcount, outError);
1830             if (outcount < newcount) return -1; /* to be sure */
1831             q = buf;
1832             newcount = 0;
1833         }
1834         if (*p == '\n') {
1835             *q++ = '\r';
1836             newcount++;
1837         } else if (((unsigned char) *p) == TN_IAC) {
1838             *q++ = (char) TN_IAC;
1839             newcount ++;
1840         }
1841         *q++ = *p++;
1842         newcount++;
1843         left--;
1844     }
1845     if (appData.debugMode) {
1846         fprintf(debugFP, ">ICS: ");
1847         show_bytes(debugFP, buf, newcount);
1848         fprintf(debugFP, "\n");
1849     }
1850     outcount = OutputToProcess(pr, buf, newcount, outError);
1851     if (outcount < newcount) return -1; /* to be sure */
1852     return count;
1853 }
1854
1855 void
1856 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1857 {
1858     int outError, outCount;
1859     static int gotEof = 0;
1860     static FILE *ini;
1861
1862     /* Pass data read from player on to ICS */
1863     if (count > 0) {
1864         gotEof = 0;
1865         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1866         if (outCount < count) {
1867             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1868         }
1869         if(have_sent_ICS_logon == 2) {
1870           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1871             fprintf(ini, "%s", message);
1872             have_sent_ICS_logon = 3;
1873           } else
1874             have_sent_ICS_logon = 1;
1875         } else if(have_sent_ICS_logon == 3) {
1876             fprintf(ini, "%s", message);
1877             fclose(ini);
1878           have_sent_ICS_logon = 1;
1879         }
1880     } else if (count < 0) {
1881         RemoveInputSource(isr);
1882         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1883     } else if (gotEof++ > 0) {
1884         RemoveInputSource(isr);
1885         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1886     }
1887 }
1888
1889 void
1890 KeepAlive ()
1891 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1892     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1893     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1894     SendToICS("date\n");
1895     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1896 }
1897
1898 /* added routine for printf style output to ics */
1899 void
1900 ics_printf (char *format, ...)
1901 {
1902     char buffer[MSG_SIZ];
1903     va_list args;
1904
1905     va_start(args, format);
1906     vsnprintf(buffer, sizeof(buffer), format, args);
1907     buffer[sizeof(buffer)-1] = '\0';
1908     SendToICS(buffer);
1909     va_end(args);
1910 }
1911
1912 void
1913 SendToICS (char *s)
1914 {
1915     int count, outCount, outError;
1916
1917     if (icsPR == NoProc) return;
1918
1919     count = strlen(s);
1920     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1921     if (outCount < count) {
1922         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1923     }
1924 }
1925
1926 /* This is used for sending logon scripts to the ICS. Sending
1927    without a delay causes problems when using timestamp on ICC
1928    (at least on my machine). */
1929 void
1930 SendToICSDelayed (char *s, long msdelay)
1931 {
1932     int count, outCount, outError;
1933
1934     if (icsPR == NoProc) return;
1935
1936     count = strlen(s);
1937     if (appData.debugMode) {
1938         fprintf(debugFP, ">ICS: ");
1939         show_bytes(debugFP, s, count);
1940         fprintf(debugFP, "\n");
1941     }
1942     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1943                                       msdelay);
1944     if (outCount < count) {
1945         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1946     }
1947 }
1948
1949
1950 /* Remove all highlighting escape sequences in s
1951    Also deletes any suffix starting with '('
1952    */
1953 char *
1954 StripHighlightAndTitle (char *s)
1955 {
1956     static char retbuf[MSG_SIZ];
1957     char *p = retbuf;
1958
1959     while (*s != NULLCHAR) {
1960         while (*s == '\033') {
1961             while (*s != NULLCHAR && !isalpha(*s)) s++;
1962             if (*s != NULLCHAR) s++;
1963         }
1964         while (*s != NULLCHAR && *s != '\033') {
1965             if (*s == '(' || *s == '[') {
1966                 *p = NULLCHAR;
1967                 return retbuf;
1968             }
1969             *p++ = *s++;
1970         }
1971     }
1972     *p = NULLCHAR;
1973     return retbuf;
1974 }
1975
1976 /* Remove all highlighting escape sequences in s */
1977 char *
1978 StripHighlight (char *s)
1979 {
1980     static char retbuf[MSG_SIZ];
1981     char *p = retbuf;
1982
1983     while (*s != NULLCHAR) {
1984         while (*s == '\033') {
1985             while (*s != NULLCHAR && !isalpha(*s)) s++;
1986             if (*s != NULLCHAR) s++;
1987         }
1988         while (*s != NULLCHAR && *s != '\033') {
1989             *p++ = *s++;
1990         }
1991     }
1992     *p = NULLCHAR;
1993     return retbuf;
1994 }
1995
1996 char *variantNames[] = VARIANT_NAMES;
1997 char *
1998 VariantName (VariantClass v)
1999 {
2000     return variantNames[v];
2001 }
2002
2003
2004 /* Identify a variant from the strings the chess servers use or the
2005    PGN Variant tag names we use. */
2006 VariantClass
2007 StringToVariant (char *e)
2008 {
2009     char *p;
2010     int wnum = -1;
2011     VariantClass v = VariantNormal;
2012     int i, found = FALSE;
2013     char buf[MSG_SIZ];
2014     int len;
2015
2016     if (!e) return v;
2017
2018     /* [HGM] skip over optional board-size prefixes */
2019     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2020         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2021         while( *e++ != '_');
2022     }
2023
2024     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2025         v = VariantNormal;
2026         found = TRUE;
2027     } else
2028     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2029       if (StrCaseStr(e, variantNames[i])) {
2030         v = (VariantClass) i;
2031         found = TRUE;
2032         break;
2033       }
2034     }
2035
2036     if (!found) {
2037       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2038           || StrCaseStr(e, "wild/fr")
2039           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2040         v = VariantFischeRandom;
2041       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2042                  (i = 1, p = StrCaseStr(e, "w"))) {
2043         p += i;
2044         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2045         if (isdigit(*p)) {
2046           wnum = atoi(p);
2047         } else {
2048           wnum = -1;
2049         }
2050         switch (wnum) {
2051         case 0: /* FICS only, actually */
2052         case 1:
2053           /* Castling legal even if K starts on d-file */
2054           v = VariantWildCastle;
2055           break;
2056         case 2:
2057         case 3:
2058         case 4:
2059           /* Castling illegal even if K & R happen to start in
2060              normal positions. */
2061           v = VariantNoCastle;
2062           break;
2063         case 5:
2064         case 7:
2065         case 8:
2066         case 10:
2067         case 11:
2068         case 12:
2069         case 13:
2070         case 14:
2071         case 15:
2072         case 18:
2073         case 19:
2074           /* Castling legal iff K & R start in normal positions */
2075           v = VariantNormal;
2076           break;
2077         case 6:
2078         case 20:
2079         case 21:
2080           /* Special wilds for position setup; unclear what to do here */
2081           v = VariantLoadable;
2082           break;
2083         case 9:
2084           /* Bizarre ICC game */
2085           v = VariantTwoKings;
2086           break;
2087         case 16:
2088           v = VariantKriegspiel;
2089           break;
2090         case 17:
2091           v = VariantLosers;
2092           break;
2093         case 22:
2094           v = VariantFischeRandom;
2095           break;
2096         case 23:
2097           v = VariantCrazyhouse;
2098           break;
2099         case 24:
2100           v = VariantBughouse;
2101           break;
2102         case 25:
2103           v = Variant3Check;
2104           break;
2105         case 26:
2106           /* Not quite the same as FICS suicide! */
2107           v = VariantGiveaway;
2108           break;
2109         case 27:
2110           v = VariantAtomic;
2111           break;
2112         case 28:
2113           v = VariantShatranj;
2114           break;
2115
2116         /* Temporary names for future ICC types.  The name *will* change in
2117            the next xboard/WinBoard release after ICC defines it. */
2118         case 29:
2119           v = Variant29;
2120           break;
2121         case 30:
2122           v = Variant30;
2123           break;
2124         case 31:
2125           v = Variant31;
2126           break;
2127         case 32:
2128           v = Variant32;
2129           break;
2130         case 33:
2131           v = Variant33;
2132           break;
2133         case 34:
2134           v = Variant34;
2135           break;
2136         case 35:
2137           v = Variant35;
2138           break;
2139         case 36:
2140           v = Variant36;
2141           break;
2142         case 37:
2143           v = VariantShogi;
2144           break;
2145         case 38:
2146           v = VariantXiangqi;
2147           break;
2148         case 39:
2149           v = VariantCourier;
2150           break;
2151         case 40:
2152           v = VariantGothic;
2153           break;
2154         case 41:
2155           v = VariantCapablanca;
2156           break;
2157         case 42:
2158           v = VariantKnightmate;
2159           break;
2160         case 43:
2161           v = VariantFairy;
2162           break;
2163         case 44:
2164           v = VariantCylinder;
2165           break;
2166         case 45:
2167           v = VariantFalcon;
2168           break;
2169         case 46:
2170           v = VariantCapaRandom;
2171           break;
2172         case 47:
2173           v = VariantBerolina;
2174           break;
2175         case 48:
2176           v = VariantJanus;
2177           break;
2178         case 49:
2179           v = VariantSuper;
2180           break;
2181         case 50:
2182           v = VariantGreat;
2183           break;
2184         case -1:
2185           /* Found "wild" or "w" in the string but no number;
2186              must assume it's normal chess. */
2187           v = VariantNormal;
2188           break;
2189         default:
2190           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2191           if( (len >= MSG_SIZ) && appData.debugMode )
2192             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2193
2194           DisplayError(buf, 0);
2195           v = VariantUnknown;
2196           break;
2197         }
2198       }
2199     }
2200     if (appData.debugMode) {
2201       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2202               e, wnum, VariantName(v));
2203     }
2204     return v;
2205 }
2206
2207 static int leftover_start = 0, leftover_len = 0;
2208 char star_match[STAR_MATCH_N][MSG_SIZ];
2209
2210 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2211    advance *index beyond it, and set leftover_start to the new value of
2212    *index; else return FALSE.  If pattern contains the character '*', it
2213    matches any sequence of characters not containing '\r', '\n', or the
2214    character following the '*' (if any), and the matched sequence(s) are
2215    copied into star_match.
2216    */
2217 int
2218 looking_at ( char *buf, int *index, char *pattern)
2219 {
2220     char *bufp = &buf[*index], *patternp = pattern;
2221     int star_count = 0;
2222     char *matchp = star_match[0];
2223
2224     for (;;) {
2225         if (*patternp == NULLCHAR) {
2226             *index = leftover_start = bufp - buf;
2227             *matchp = NULLCHAR;
2228             return TRUE;
2229         }
2230         if (*bufp == NULLCHAR) return FALSE;
2231         if (*patternp == '*') {
2232             if (*bufp == *(patternp + 1)) {
2233                 *matchp = NULLCHAR;
2234                 matchp = star_match[++star_count];
2235                 patternp += 2;
2236                 bufp++;
2237                 continue;
2238             } else if (*bufp == '\n' || *bufp == '\r') {
2239                 patternp++;
2240                 if (*patternp == NULLCHAR)
2241                   continue;
2242                 else
2243                   return FALSE;
2244             } else {
2245                 *matchp++ = *bufp++;
2246                 continue;
2247             }
2248         }
2249         if (*patternp != *bufp) return FALSE;
2250         patternp++;
2251         bufp++;
2252     }
2253 }
2254
2255 void
2256 SendToPlayer (char *data, int length)
2257 {
2258     int error, outCount;
2259     outCount = OutputToProcess(NoProc, data, length, &error);
2260     if (outCount < length) {
2261         DisplayFatalError(_("Error writing to display"), error, 1);
2262     }
2263 }
2264
2265 void
2266 PackHolding (char packed[], char *holding)
2267 {
2268     char *p = holding;
2269     char *q = packed;
2270     int runlength = 0;
2271     int curr = 9999;
2272     do {
2273         if (*p == curr) {
2274             runlength++;
2275         } else {
2276             switch (runlength) {
2277               case 0:
2278                 break;
2279               case 1:
2280                 *q++ = curr;
2281                 break;
2282               case 2:
2283                 *q++ = curr;
2284                 *q++ = curr;
2285                 break;
2286               default:
2287                 sprintf(q, "%d", runlength);
2288                 while (*q) q++;
2289                 *q++ = curr;
2290                 break;
2291             }
2292             runlength = 1;
2293             curr = *p;
2294         }
2295     } while (*p++);
2296     *q = NULLCHAR;
2297 }
2298
2299 /* Telnet protocol requests from the front end */
2300 void
2301 TelnetRequest (unsigned char ddww, unsigned char option)
2302 {
2303     unsigned char msg[3];
2304     int outCount, outError;
2305
2306     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2307
2308     if (appData.debugMode) {
2309         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2310         switch (ddww) {
2311           case TN_DO:
2312             ddwwStr = "DO";
2313             break;
2314           case TN_DONT:
2315             ddwwStr = "DONT";
2316             break;
2317           case TN_WILL:
2318             ddwwStr = "WILL";
2319             break;
2320           case TN_WONT:
2321             ddwwStr = "WONT";
2322             break;
2323           default:
2324             ddwwStr = buf1;
2325             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2326             break;
2327         }
2328         switch (option) {
2329           case TN_ECHO:
2330             optionStr = "ECHO";
2331             break;
2332           default:
2333             optionStr = buf2;
2334             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2335             break;
2336         }
2337         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2338     }
2339     msg[0] = TN_IAC;
2340     msg[1] = ddww;
2341     msg[2] = option;
2342     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2343     if (outCount < 3) {
2344         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2345     }
2346 }
2347
2348 void
2349 DoEcho ()
2350 {
2351     if (!appData.icsActive) return;
2352     TelnetRequest(TN_DO, TN_ECHO);
2353 }
2354
2355 void
2356 DontEcho ()
2357 {
2358     if (!appData.icsActive) return;
2359     TelnetRequest(TN_DONT, TN_ECHO);
2360 }
2361
2362 void
2363 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2364 {
2365     /* put the holdings sent to us by the server on the board holdings area */
2366     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2367     char p;
2368     ChessSquare piece;
2369
2370     if(gameInfo.holdingsWidth < 2)  return;
2371     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2372         return; // prevent overwriting by pre-board holdings
2373
2374     if( (int)lowestPiece >= BlackPawn ) {
2375         holdingsColumn = 0;
2376         countsColumn = 1;
2377         holdingsStartRow = BOARD_HEIGHT-1;
2378         direction = -1;
2379     } else {
2380         holdingsColumn = BOARD_WIDTH-1;
2381         countsColumn = BOARD_WIDTH-2;
2382         holdingsStartRow = 0;
2383         direction = 1;
2384     }
2385
2386     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2387         board[i][holdingsColumn] = EmptySquare;
2388         board[i][countsColumn]   = (ChessSquare) 0;
2389     }
2390     while( (p=*holdings++) != NULLCHAR ) {
2391         piece = CharToPiece( ToUpper(p) );
2392         if(piece == EmptySquare) continue;
2393         /*j = (int) piece - (int) WhitePawn;*/
2394         j = PieceToNumber(piece);
2395         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2396         if(j < 0) continue;               /* should not happen */
2397         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2398         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2399         board[holdingsStartRow+j*direction][countsColumn]++;
2400     }
2401 }
2402
2403
2404 void
2405 VariantSwitch (Board board, VariantClass newVariant)
2406 {
2407    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2408    static Board oldBoard;
2409
2410    startedFromPositionFile = FALSE;
2411    if(gameInfo.variant == newVariant) return;
2412
2413    /* [HGM] This routine is called each time an assignment is made to
2414     * gameInfo.variant during a game, to make sure the board sizes
2415     * are set to match the new variant. If that means adding or deleting
2416     * holdings, we shift the playing board accordingly
2417     * This kludge is needed because in ICS observe mode, we get boards
2418     * of an ongoing game without knowing the variant, and learn about the
2419     * latter only later. This can be because of the move list we requested,
2420     * in which case the game history is refilled from the beginning anyway,
2421     * but also when receiving holdings of a crazyhouse game. In the latter
2422     * case we want to add those holdings to the already received position.
2423     */
2424
2425
2426    if (appData.debugMode) {
2427      fprintf(debugFP, "Switch board from %s to %s\n",
2428              VariantName(gameInfo.variant), VariantName(newVariant));
2429      setbuf(debugFP, NULL);
2430    }
2431    shuffleOpenings = 0;       /* [HGM] shuffle */
2432    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2433    switch(newVariant)
2434      {
2435      case VariantShogi:
2436        newWidth = 9;  newHeight = 9;
2437        gameInfo.holdingsSize = 7;
2438      case VariantBughouse:
2439      case VariantCrazyhouse:
2440        newHoldingsWidth = 2; break;
2441      case VariantGreat:
2442        newWidth = 10;
2443      case VariantSuper:
2444        newHoldingsWidth = 2;
2445        gameInfo.holdingsSize = 8;
2446        break;
2447      case VariantGothic:
2448      case VariantCapablanca:
2449      case VariantCapaRandom:
2450        newWidth = 10;
2451      default:
2452        newHoldingsWidth = gameInfo.holdingsSize = 0;
2453      };
2454
2455    if(newWidth  != gameInfo.boardWidth  ||
2456       newHeight != gameInfo.boardHeight ||
2457       newHoldingsWidth != gameInfo.holdingsWidth ) {
2458
2459      /* shift position to new playing area, if needed */
2460      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2461        for(i=0; i<BOARD_HEIGHT; i++)
2462          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2463            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2464              board[i][j];
2465        for(i=0; i<newHeight; i++) {
2466          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2467          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2468        }
2469      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2470        for(i=0; i<BOARD_HEIGHT; i++)
2471          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2472            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2473              board[i][j];
2474      }
2475      board[HOLDINGS_SET] = 0;
2476      gameInfo.boardWidth  = newWidth;
2477      gameInfo.boardHeight = newHeight;
2478      gameInfo.holdingsWidth = newHoldingsWidth;
2479      gameInfo.variant = newVariant;
2480      InitDrawingSizes(-2, 0);
2481    } else gameInfo.variant = newVariant;
2482    CopyBoard(oldBoard, board);   // remember correctly formatted board
2483      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2484    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2485 }
2486
2487 static int loggedOn = FALSE;
2488
2489 /*-- Game start info cache: --*/
2490 int gs_gamenum;
2491 char gs_kind[MSG_SIZ];
2492 static char player1Name[128] = "";
2493 static char player2Name[128] = "";
2494 static char cont_seq[] = "\n\\   ";
2495 static int player1Rating = -1;
2496 static int player2Rating = -1;
2497 /*----------------------------*/
2498
2499 ColorClass curColor = ColorNormal;
2500 int suppressKibitz = 0;
2501
2502 // [HGM] seekgraph
2503 Boolean soughtPending = FALSE;
2504 Boolean seekGraphUp;
2505 #define MAX_SEEK_ADS 200
2506 #define SQUARE 0x80
2507 char *seekAdList[MAX_SEEK_ADS];
2508 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2509 float tcList[MAX_SEEK_ADS];
2510 char colorList[MAX_SEEK_ADS];
2511 int nrOfSeekAds = 0;
2512 int minRating = 1010, maxRating = 2800;
2513 int hMargin = 10, vMargin = 20, h, w;
2514 extern int squareSize, lineGap;
2515
2516 void
2517 PlotSeekAd (int i)
2518 {
2519         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2520         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2521         if(r < minRating+100 && r >=0 ) r = minRating+100;
2522         if(r > maxRating) r = maxRating;
2523         if(tc < 1.f) tc = 1.f;
2524         if(tc > 95.f) tc = 95.f;
2525         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2526         y = ((double)r - minRating)/(maxRating - minRating)
2527             * (h-vMargin-squareSize/8-1) + vMargin;
2528         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2529         if(strstr(seekAdList[i], " u ")) color = 1;
2530         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2531            !strstr(seekAdList[i], "bullet") &&
2532            !strstr(seekAdList[i], "blitz") &&
2533            !strstr(seekAdList[i], "standard") ) color = 2;
2534         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2535         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2536 }
2537
2538 void
2539 PlotSingleSeekAd (int i)
2540 {
2541         PlotSeekAd(i);
2542 }
2543
2544 void
2545 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2546 {
2547         char buf[MSG_SIZ], *ext = "";
2548         VariantClass v = StringToVariant(type);
2549         if(strstr(type, "wild")) {
2550             ext = type + 4; // append wild number
2551             if(v == VariantFischeRandom) type = "chess960"; else
2552             if(v == VariantLoadable) type = "setup"; else
2553             type = VariantName(v);
2554         }
2555         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2556         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2557             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2558             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2559             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2560             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2561             seekNrList[nrOfSeekAds] = nr;
2562             zList[nrOfSeekAds] = 0;
2563             seekAdList[nrOfSeekAds++] = StrSave(buf);
2564             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2565         }
2566 }
2567
2568 void
2569 EraseSeekDot (int i)
2570 {
2571     int x = xList[i], y = yList[i], d=squareSize/4, k;
2572     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2573     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2574     // now replot every dot that overlapped
2575     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2576         int xx = xList[k], yy = yList[k];
2577         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2578             DrawSeekDot(xx, yy, colorList[k]);
2579     }
2580 }
2581
2582 void
2583 RemoveSeekAd (int nr)
2584 {
2585         int i;
2586         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2587             EraseSeekDot(i);
2588             if(seekAdList[i]) free(seekAdList[i]);
2589             seekAdList[i] = seekAdList[--nrOfSeekAds];
2590             seekNrList[i] = seekNrList[nrOfSeekAds];
2591             ratingList[i] = ratingList[nrOfSeekAds];
2592             colorList[i]  = colorList[nrOfSeekAds];
2593             tcList[i] = tcList[nrOfSeekAds];
2594             xList[i]  = xList[nrOfSeekAds];
2595             yList[i]  = yList[nrOfSeekAds];
2596             zList[i]  = zList[nrOfSeekAds];
2597             seekAdList[nrOfSeekAds] = NULL;
2598             break;
2599         }
2600 }
2601
2602 Boolean
2603 MatchSoughtLine (char *line)
2604 {
2605     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2606     int nr, base, inc, u=0; char dummy;
2607
2608     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2609        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2610        (u=1) &&
2611        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2612         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2613         // match: compact and save the line
2614         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2615         return TRUE;
2616     }
2617     return FALSE;
2618 }
2619
2620 int
2621 DrawSeekGraph ()
2622 {
2623     int i;
2624     if(!seekGraphUp) return FALSE;
2625     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2626     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2627
2628     DrawSeekBackground(0, 0, w, h);
2629     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2630     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2631     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2632         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2633         yy = h-1-yy;
2634         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2635         if(i%500 == 0) {
2636             char buf[MSG_SIZ];
2637             snprintf(buf, MSG_SIZ, "%d", i);
2638             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2639         }
2640     }
2641     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2642     for(i=1; i<100; i+=(i<10?1:5)) {
2643         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2644         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2645         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2646             char buf[MSG_SIZ];
2647             snprintf(buf, MSG_SIZ, "%d", i);
2648             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2649         }
2650     }
2651     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2652     return TRUE;
2653 }
2654
2655 int
2656 SeekGraphClick (ClickType click, int x, int y, int moving)
2657 {
2658     static int lastDown = 0, displayed = 0, lastSecond;
2659     if(y < 0) return FALSE;
2660     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2661         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2662         if(!seekGraphUp) return FALSE;
2663         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2664         DrawPosition(TRUE, NULL);
2665         return TRUE;
2666     }
2667     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2668         if(click == Release || moving) return FALSE;
2669         nrOfSeekAds = 0;
2670         soughtPending = TRUE;
2671         SendToICS(ics_prefix);
2672         SendToICS("sought\n"); // should this be "sought all"?
2673     } else { // issue challenge based on clicked ad
2674         int dist = 10000; int i, closest = 0, second = 0;
2675         for(i=0; i<nrOfSeekAds; i++) {
2676             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2677             if(d < dist) { dist = d; closest = i; }
2678             second += (d - zList[i] < 120); // count in-range ads
2679             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2680         }
2681         if(dist < 120) {
2682             char buf[MSG_SIZ];
2683             second = (second > 1);
2684             if(displayed != closest || second != lastSecond) {
2685                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2686                 lastSecond = second; displayed = closest;
2687             }
2688             if(click == Press) {
2689                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2690                 lastDown = closest;
2691                 return TRUE;
2692             } // on press 'hit', only show info
2693             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2694             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2695             SendToICS(ics_prefix);
2696             SendToICS(buf);
2697             return TRUE; // let incoming board of started game pop down the graph
2698         } else if(click == Release) { // release 'miss' is ignored
2699             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2700             if(moving == 2) { // right up-click
2701                 nrOfSeekAds = 0; // refresh graph
2702                 soughtPending = TRUE;
2703                 SendToICS(ics_prefix);
2704                 SendToICS("sought\n"); // should this be "sought all"?
2705             }
2706             return TRUE;
2707         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2708         // press miss or release hit 'pop down' seek graph
2709         seekGraphUp = FALSE;
2710         DrawPosition(TRUE, NULL);
2711     }
2712     return TRUE;
2713 }
2714
2715 void
2716 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2717 {
2718 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2719 #define STARTED_NONE 0
2720 #define STARTED_MOVES 1
2721 #define STARTED_BOARD 2
2722 #define STARTED_OBSERVE 3
2723 #define STARTED_HOLDINGS 4
2724 #define STARTED_CHATTER 5
2725 #define STARTED_COMMENT 6
2726 #define STARTED_MOVES_NOHIDE 7
2727
2728     static int started = STARTED_NONE;
2729     static char parse[20000];
2730     static int parse_pos = 0;
2731     static char buf[BUF_SIZE + 1];
2732     static int firstTime = TRUE, intfSet = FALSE;
2733     static ColorClass prevColor = ColorNormal;
2734     static int savingComment = FALSE;
2735     static int cmatch = 0; // continuation sequence match
2736     char *bp;
2737     char str[MSG_SIZ];
2738     int i, oldi;
2739     int buf_len;
2740     int next_out;
2741     int tkind;
2742     int backup;    /* [DM] For zippy color lines */
2743     char *p;
2744     char talker[MSG_SIZ]; // [HGM] chat
2745     int channel;
2746
2747     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2748
2749     if (appData.debugMode) {
2750       if (!error) {
2751         fprintf(debugFP, "<ICS: ");
2752         show_bytes(debugFP, data, count);
2753         fprintf(debugFP, "\n");
2754       }
2755     }
2756
2757     if (appData.debugMode) { int f = forwardMostMove;
2758         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2759                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2760                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2761     }
2762     if (count > 0) {
2763         /* If last read ended with a partial line that we couldn't parse,
2764            prepend it to the new read and try again. */
2765         if (leftover_len > 0) {
2766             for (i=0; i<leftover_len; i++)
2767               buf[i] = buf[leftover_start + i];
2768         }
2769
2770     /* copy new characters into the buffer */
2771     bp = buf + leftover_len;
2772     buf_len=leftover_len;
2773     for (i=0; i<count; i++)
2774     {
2775         // ignore these
2776         if (data[i] == '\r')
2777             continue;
2778
2779         // join lines split by ICS?
2780         if (!appData.noJoin)
2781         {
2782             /*
2783                 Joining just consists of finding matches against the
2784                 continuation sequence, and discarding that sequence
2785                 if found instead of copying it.  So, until a match
2786                 fails, there's nothing to do since it might be the
2787                 complete sequence, and thus, something we don't want
2788                 copied.
2789             */
2790             if (data[i] == cont_seq[cmatch])
2791             {
2792                 cmatch++;
2793                 if (cmatch == strlen(cont_seq))
2794                 {
2795                     cmatch = 0; // complete match.  just reset the counter
2796
2797                     /*
2798                         it's possible for the ICS to not include the space
2799                         at the end of the last word, making our [correct]
2800                         join operation fuse two separate words.  the server
2801                         does this when the space occurs at the width setting.
2802                     */
2803                     if (!buf_len || buf[buf_len-1] != ' ')
2804                     {
2805                         *bp++ = ' ';
2806                         buf_len++;
2807                     }
2808                 }
2809                 continue;
2810             }
2811             else if (cmatch)
2812             {
2813                 /*
2814                     match failed, so we have to copy what matched before
2815                     falling through and copying this character.  In reality,
2816                     this will only ever be just the newline character, but
2817                     it doesn't hurt to be precise.
2818                 */
2819                 strncpy(bp, cont_seq, cmatch);
2820                 bp += cmatch;
2821                 buf_len += cmatch;
2822                 cmatch = 0;
2823             }
2824         }
2825
2826         // copy this char
2827         *bp++ = data[i];
2828         buf_len++;
2829     }
2830
2831         buf[buf_len] = NULLCHAR;
2832 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2833         next_out = 0;
2834         leftover_start = 0;
2835
2836         i = 0;
2837         while (i < buf_len) {
2838             /* Deal with part of the TELNET option negotiation
2839                protocol.  We refuse to do anything beyond the
2840                defaults, except that we allow the WILL ECHO option,
2841                which ICS uses to turn off password echoing when we are
2842                directly connected to it.  We reject this option
2843                if localLineEditing mode is on (always on in xboard)
2844                and we are talking to port 23, which might be a real
2845                telnet server that will try to keep WILL ECHO on permanently.
2846              */
2847             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2848                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2849                 unsigned char option;
2850                 oldi = i;
2851                 switch ((unsigned char) buf[++i]) {
2852                   case TN_WILL:
2853                     if (appData.debugMode)
2854                       fprintf(debugFP, "\n<WILL ");
2855                     switch (option = (unsigned char) buf[++i]) {
2856                       case TN_ECHO:
2857                         if (appData.debugMode)
2858                           fprintf(debugFP, "ECHO ");
2859                         /* Reply only if this is a change, according
2860                            to the protocol rules. */
2861                         if (remoteEchoOption) break;
2862                         if (appData.localLineEditing &&
2863                             atoi(appData.icsPort) == TN_PORT) {
2864                             TelnetRequest(TN_DONT, TN_ECHO);
2865                         } else {
2866                             EchoOff();
2867                             TelnetRequest(TN_DO, TN_ECHO);
2868                             remoteEchoOption = TRUE;
2869                         }
2870                         break;
2871                       default:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "%d ", option);
2874                         /* Whatever this is, we don't want it. */
2875                         TelnetRequest(TN_DONT, option);
2876                         break;
2877                     }
2878                     break;
2879                   case TN_WONT:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<WONT ");
2882                     switch (option = (unsigned char) buf[++i]) {
2883                       case TN_ECHO:
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "ECHO ");
2886                         /* Reply only if this is a change, according
2887                            to the protocol rules. */
2888                         if (!remoteEchoOption) break;
2889                         EchoOn();
2890                         TelnetRequest(TN_DONT, TN_ECHO);
2891                         remoteEchoOption = FALSE;
2892                         break;
2893                       default:
2894                         if (appData.debugMode)
2895                           fprintf(debugFP, "%d ", (unsigned char) option);
2896                         /* Whatever this is, it must already be turned
2897                            off, because we never agree to turn on
2898                            anything non-default, so according to the
2899                            protocol rules, we don't reply. */
2900                         break;
2901                     }
2902                     break;
2903                   case TN_DO:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<DO ");
2906                     switch (option = (unsigned char) buf[++i]) {
2907                       default:
2908                         /* Whatever this is, we refuse to do it. */
2909                         if (appData.debugMode)
2910                           fprintf(debugFP, "%d ", option);
2911                         TelnetRequest(TN_WONT, option);
2912                         break;
2913                     }
2914                     break;
2915                   case TN_DONT:
2916                     if (appData.debugMode)
2917                       fprintf(debugFP, "\n<DONT ");
2918                     switch (option = (unsigned char) buf[++i]) {
2919                       default:
2920                         if (appData.debugMode)
2921                           fprintf(debugFP, "%d ", option);
2922                         /* Whatever this is, we are already not doing
2923                            it, because we never agree to do anything
2924                            non-default, so according to the protocol
2925                            rules, we don't reply. */
2926                         break;
2927                     }
2928                     break;
2929                   case TN_IAC:
2930                     if (appData.debugMode)
2931                       fprintf(debugFP, "\n<IAC ");
2932                     /* Doubled IAC; pass it through */
2933                     i--;
2934                     break;
2935                   default:
2936                     if (appData.debugMode)
2937                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2938                     /* Drop all other telnet commands on the floor */
2939                     break;
2940                 }
2941                 if (oldi > next_out)
2942                   SendToPlayer(&buf[next_out], oldi - next_out);
2943                 if (++i > next_out)
2944                   next_out = i;
2945                 continue;
2946             }
2947
2948             /* OK, this at least will *usually* work */
2949             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2950                 loggedOn = TRUE;
2951             }
2952
2953             if (loggedOn && !intfSet) {
2954                 if (ics_type == ICS_ICC) {
2955                   snprintf(str, MSG_SIZ,
2956                           "/set-quietly interface %s\n/set-quietly style 12\n",
2957                           programVersion);
2958                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2959                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2960                 } else if (ics_type == ICS_CHESSNET) {
2961                   snprintf(str, MSG_SIZ, "/style 12\n");
2962                 } else {
2963                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2964                   strcat(str, programVersion);
2965                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2966                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2967                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2968 #ifdef WIN32
2969                   strcat(str, "$iset nohighlight 1\n");
2970 #endif
2971                   strcat(str, "$iset lock 1\n$style 12\n");
2972                 }
2973                 SendToICS(str);
2974                 NotifyFrontendLogin();
2975                 intfSet = TRUE;
2976             }
2977
2978             if (started == STARTED_COMMENT) {
2979                 /* Accumulate characters in comment */
2980                 parse[parse_pos++] = buf[i];
2981                 if (buf[i] == '\n') {
2982                     parse[parse_pos] = NULLCHAR;
2983                     if(chattingPartner>=0) {
2984                         char mess[MSG_SIZ];
2985                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2986                         OutputChatMessage(chattingPartner, mess);
2987                         chattingPartner = -1;
2988                         next_out = i+1; // [HGM] suppress printing in ICS window
2989                     } else
2990                     if(!suppressKibitz) // [HGM] kibitz
2991                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2992                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2993                         int nrDigit = 0, nrAlph = 0, j;
2994                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2995                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2996                         parse[parse_pos] = NULLCHAR;
2997                         // try to be smart: if it does not look like search info, it should go to
2998                         // ICS interaction window after all, not to engine-output window.
2999                         for(j=0; j<parse_pos; j++) { // count letters and digits
3000                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3001                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3002                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3003                         }
3004                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3005                             int depth=0; float score;
3006                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3007                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3008                                 pvInfoList[forwardMostMove-1].depth = depth;
3009                                 pvInfoList[forwardMostMove-1].score = 100*score;
3010                             }
3011                             OutputKibitz(suppressKibitz, parse);
3012                         } else {
3013                             char tmp[MSG_SIZ];
3014                             if(gameMode == IcsObserving) // restore original ICS messages
3015                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3016                             else
3017                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3018                             SendToPlayer(tmp, strlen(tmp));
3019                         }
3020                         next_out = i+1; // [HGM] suppress printing in ICS window
3021                     }
3022                     started = STARTED_NONE;
3023                 } else {
3024                     /* Don't match patterns against characters in comment */
3025                     i++;
3026                     continue;
3027                 }
3028             }
3029             if (started == STARTED_CHATTER) {
3030                 if (buf[i] != '\n') {
3031                     /* Don't match patterns against characters in chatter */
3032                     i++;
3033                     continue;
3034                 }
3035                 started = STARTED_NONE;
3036                 if(suppressKibitz) next_out = i+1;
3037             }
3038
3039             /* Kludge to deal with rcmd protocol */
3040             if (firstTime && looking_at(buf, &i, "\001*")) {
3041                 DisplayFatalError(&buf[1], 0, 1);
3042                 continue;
3043             } else {
3044                 firstTime = FALSE;
3045             }
3046
3047             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3048                 ics_type = ICS_ICC;
3049                 ics_prefix = "/";
3050                 if (appData.debugMode)
3051                   fprintf(debugFP, "ics_type %d\n", ics_type);
3052                 continue;
3053             }
3054             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3055                 ics_type = ICS_FICS;
3056                 ics_prefix = "$";
3057                 if (appData.debugMode)
3058                   fprintf(debugFP, "ics_type %d\n", ics_type);
3059                 continue;
3060             }
3061             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3062                 ics_type = ICS_CHESSNET;
3063                 ics_prefix = "/";
3064                 if (appData.debugMode)
3065                   fprintf(debugFP, "ics_type %d\n", ics_type);
3066                 continue;
3067             }
3068
3069             if (!loggedOn &&
3070                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3071                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3072                  looking_at(buf, &i, "will be \"*\""))) {
3073               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3074               continue;
3075             }
3076
3077             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3078               char buf[MSG_SIZ];
3079               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3080               DisplayIcsInteractionTitle(buf);
3081               have_set_title = TRUE;
3082             }
3083
3084             /* skip finger notes */
3085             if (started == STARTED_NONE &&
3086                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3087                  (buf[i] == '1' && buf[i+1] == '0')) &&
3088                 buf[i+2] == ':' && buf[i+3] == ' ') {
3089               started = STARTED_CHATTER;
3090               i += 3;
3091               continue;
3092             }
3093
3094             oldi = i;
3095             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3096             if(appData.seekGraph) {
3097                 if(soughtPending && MatchSoughtLine(buf+i)) {
3098                     i = strstr(buf+i, "rated") - buf;
3099                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                     next_out = leftover_start = i;
3101                     started = STARTED_CHATTER;
3102                     suppressKibitz = TRUE;
3103                     continue;
3104                 }
3105                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3106                         && looking_at(buf, &i, "* ads displayed")) {
3107                     soughtPending = FALSE;
3108                     seekGraphUp = TRUE;
3109                     DrawSeekGraph();
3110                     continue;
3111                 }
3112                 if(appData.autoRefresh) {
3113                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3114                         int s = (ics_type == ICS_ICC); // ICC format differs
3115                         if(seekGraphUp)
3116                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3117                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3118                         looking_at(buf, &i, "*% "); // eat prompt
3119                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3120                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3121                         next_out = i; // suppress
3122                         continue;
3123                     }
3124                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3125                         char *p = star_match[0];
3126                         while(*p) {
3127                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3128                             while(*p && *p++ != ' '); // next
3129                         }
3130                         looking_at(buf, &i, "*% "); // eat prompt
3131                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132                         next_out = i;
3133                         continue;
3134                     }
3135                 }
3136             }
3137
3138             /* skip formula vars */
3139             if (started == STARTED_NONE &&
3140                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3141               started = STARTED_CHATTER;
3142               i += 3;
3143               continue;
3144             }
3145
3146             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3147             if (appData.autoKibitz && started == STARTED_NONE &&
3148                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3149                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3150                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3151                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3152                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3153                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3154                         suppressKibitz = TRUE;
3155                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156                         next_out = i;
3157                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3158                                 && (gameMode == IcsPlayingWhite)) ||
3159                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3160                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3161                             started = STARTED_CHATTER; // own kibitz we simply discard
3162                         else {
3163                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3164                             parse_pos = 0; parse[0] = NULLCHAR;
3165                             savingComment = TRUE;
3166                             suppressKibitz = gameMode != IcsObserving ? 2 :
3167                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3168                         }
3169                         continue;
3170                 } else
3171                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3172                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3173                          && atoi(star_match[0])) {
3174                     // suppress the acknowledgements of our own autoKibitz
3175                     char *p;
3176                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3177                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3178                     SendToPlayer(star_match[0], strlen(star_match[0]));
3179                     if(looking_at(buf, &i, "*% ")) // eat prompt
3180                         suppressKibitz = FALSE;
3181                     next_out = i;
3182                     continue;
3183                 }
3184             } // [HGM] kibitz: end of patch
3185
3186             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3187
3188             // [HGM] chat: intercept tells by users for which we have an open chat window
3189             channel = -1;
3190             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3191                                            looking_at(buf, &i, "* whispers:") ||
3192                                            looking_at(buf, &i, "* kibitzes:") ||
3193                                            looking_at(buf, &i, "* shouts:") ||
3194                                            looking_at(buf, &i, "* c-shouts:") ||
3195                                            looking_at(buf, &i, "--> * ") ||
3196                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3197                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3198                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3199                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3200                 int p;
3201                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3202                 chattingPartner = -1;
3203
3204                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3205                 for(p=0; p<MAX_CHAT; p++) {
3206                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3207                     talker[0] = '['; strcat(talker, "] ");
3208                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3209                     chattingPartner = p; break;
3210                     }
3211                 } else
3212                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3213                 for(p=0; p<MAX_CHAT; p++) {
3214                     if(!strcmp("kibitzes", chatPartner[p])) {
3215                         talker[0] = '['; strcat(talker, "] ");
3216                         chattingPartner = p; break;
3217                     }
3218                 } else
3219                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3220                 for(p=0; p<MAX_CHAT; p++) {
3221                     if(!strcmp("whispers", chatPartner[p])) {
3222                         talker[0] = '['; strcat(talker, "] ");
3223                         chattingPartner = p; break;
3224                     }
3225                 } else
3226                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3227                   if(buf[i-8] == '-' && buf[i-3] == 't')
3228                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3229                     if(!strcmp("c-shouts", chatPartner[p])) {
3230                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3231                         chattingPartner = p; break;
3232                     }
3233                   }
3234                   if(chattingPartner < 0)
3235                   for(p=0; p<MAX_CHAT; p++) {
3236                     if(!strcmp("shouts", chatPartner[p])) {
3237                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3238                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3239                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3240                         chattingPartner = p; break;
3241                     }
3242                   }
3243                 }
3244                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3245                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3246                     talker[0] = 0; Colorize(ColorTell, FALSE);
3247                     chattingPartner = p; break;
3248                 }
3249                 if(chattingPartner<0) i = oldi; else {
3250                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3251                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3252                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253                     started = STARTED_COMMENT;
3254                     parse_pos = 0; parse[0] = NULLCHAR;
3255                     savingComment = 3 + chattingPartner; // counts as TRUE
3256                     suppressKibitz = TRUE;
3257                     continue;
3258                 }
3259             } // [HGM] chat: end of patch
3260
3261           backup = i;
3262             if (appData.zippyTalk || appData.zippyPlay) {
3263                 /* [DM] Backup address for color zippy lines */
3264 #if ZIPPY
3265                if (loggedOn == TRUE)
3266                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3267                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3268 #endif
3269             } // [DM] 'else { ' deleted
3270                 if (
3271                     /* Regular tells and says */
3272                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3273                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3274                     looking_at(buf, &i, "* says: ") ||
3275                     /* Don't color "message" or "messages" output */
3276                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3277                     looking_at(buf, &i, "*. * at *:*: ") ||
3278                     looking_at(buf, &i, "--* (*:*): ") ||
3279                     /* Message notifications (same color as tells) */
3280                     looking_at(buf, &i, "* has left a message ") ||
3281                     looking_at(buf, &i, "* just sent you a message:\n") ||
3282                     /* Whispers and kibitzes */
3283                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3284                     looking_at(buf, &i, "* kibitzes: ") ||
3285                     /* Channel tells */
3286                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3287
3288                   if (tkind == 1 && strchr(star_match[0], ':')) {
3289                       /* Avoid "tells you:" spoofs in channels */
3290                      tkind = 3;
3291                   }
3292                   if (star_match[0][0] == NULLCHAR ||
3293                       strchr(star_match[0], ' ') ||
3294                       (tkind == 3 && strchr(star_match[1], ' '))) {
3295                     /* Reject bogus matches */
3296                     i = oldi;
3297                   } else {
3298                     if (appData.colorize) {
3299                       if (oldi > next_out) {
3300                         SendToPlayer(&buf[next_out], oldi - next_out);
3301                         next_out = oldi;
3302                       }
3303                       switch (tkind) {
3304                       case 1:
3305                         Colorize(ColorTell, FALSE);
3306                         curColor = ColorTell;
3307                         break;
3308                       case 2:
3309                         Colorize(ColorKibitz, FALSE);
3310                         curColor = ColorKibitz;
3311                         break;
3312                       case 3:
3313                         p = strrchr(star_match[1], '(');
3314                         if (p == NULL) {
3315                           p = star_match[1];
3316                         } else {
3317                           p++;
3318                         }
3319                         if (atoi(p) == 1) {
3320                           Colorize(ColorChannel1, FALSE);
3321                           curColor = ColorChannel1;
3322                         } else {
3323                           Colorize(ColorChannel, FALSE);
3324                           curColor = ColorChannel;
3325                         }
3326                         break;
3327                       case 5:
3328                         curColor = ColorNormal;
3329                         break;
3330                       }
3331                     }
3332                     if (started == STARTED_NONE && appData.autoComment &&
3333                         (gameMode == IcsObserving ||
3334                          gameMode == IcsPlayingWhite ||
3335                          gameMode == IcsPlayingBlack)) {
3336                       parse_pos = i - oldi;
3337                       memcpy(parse, &buf[oldi], parse_pos);
3338                       parse[parse_pos] = NULLCHAR;
3339                       started = STARTED_COMMENT;
3340                       savingComment = TRUE;
3341                     } else {
3342                       started = STARTED_CHATTER;
3343                       savingComment = FALSE;
3344                     }
3345                     loggedOn = TRUE;
3346                     continue;
3347                   }
3348                 }
3349
3350                 if (looking_at(buf, &i, "* s-shouts: ") ||
3351                     looking_at(buf, &i, "* c-shouts: ")) {
3352                     if (appData.colorize) {
3353                         if (oldi > next_out) {
3354                             SendToPlayer(&buf[next_out], oldi - next_out);
3355                             next_out = oldi;
3356                         }
3357                         Colorize(ColorSShout, FALSE);
3358                         curColor = ColorSShout;
3359                     }
3360                     loggedOn = TRUE;
3361                     started = STARTED_CHATTER;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "--->")) {
3366                     loggedOn = TRUE;
3367                     continue;
3368                 }
3369
3370                 if (looking_at(buf, &i, "* shouts: ") ||
3371                     looking_at(buf, &i, "--> ")) {
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorShout, FALSE);
3378                         curColor = ColorShout;
3379                     }
3380                     loggedOn = TRUE;
3381                     started = STARTED_CHATTER;
3382                     continue;
3383                 }
3384
3385                 if (looking_at( buf, &i, "Challenge:")) {
3386                     if (appData.colorize) {
3387                         if (oldi > next_out) {
3388                             SendToPlayer(&buf[next_out], oldi - next_out);
3389                             next_out = oldi;
3390                         }
3391                         Colorize(ColorChallenge, FALSE);
3392                         curColor = ColorChallenge;
3393                     }
3394                     loggedOn = TRUE;
3395                     continue;
3396                 }
3397
3398                 if (looking_at(buf, &i, "* offers you") ||
3399                     looking_at(buf, &i, "* offers to be") ||
3400                     looking_at(buf, &i, "* would like to") ||
3401                     looking_at(buf, &i, "* requests to") ||
3402                     looking_at(buf, &i, "Your opponent offers") ||
3403                     looking_at(buf, &i, "Your opponent requests")) {
3404
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorRequest, FALSE);
3411                         curColor = ColorRequest;
3412                     }
3413                     continue;
3414                 }
3415
3416                 if (looking_at(buf, &i, "* (*) seeking")) {
3417                     if (appData.colorize) {
3418                         if (oldi > next_out) {
3419                             SendToPlayer(&buf[next_out], oldi - next_out);
3420                             next_out = oldi;
3421                         }
3422                         Colorize(ColorSeek, FALSE);
3423                         curColor = ColorSeek;
3424                     }
3425                     continue;
3426             }
3427
3428           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3429
3430             if (looking_at(buf, &i, "\\   ")) {
3431                 if (prevColor != ColorNormal) {
3432                     if (oldi > next_out) {
3433                         SendToPlayer(&buf[next_out], oldi - next_out);
3434                         next_out = oldi;
3435                     }
3436                     Colorize(prevColor, TRUE);
3437                     curColor = prevColor;
3438                 }
3439                 if (savingComment) {
3440                     parse_pos = i - oldi;
3441                     memcpy(parse, &buf[oldi], parse_pos);
3442                     parse[parse_pos] = NULLCHAR;
3443                     started = STARTED_COMMENT;
3444                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3445                         chattingPartner = savingComment - 3; // kludge to remember the box
3446                 } else {
3447                     started = STARTED_CHATTER;
3448                 }
3449                 continue;
3450             }
3451
3452             if (looking_at(buf, &i, "Black Strength :") ||
3453                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3454                 looking_at(buf, &i, "<10>") ||
3455                 looking_at(buf, &i, "#@#")) {
3456                 /* Wrong board style */
3457                 loggedOn = TRUE;
3458                 SendToICS(ics_prefix);
3459                 SendToICS("set style 12\n");
3460                 SendToICS(ics_prefix);
3461                 SendToICS("refresh\n");
3462                 continue;
3463             }
3464
3465             if (looking_at(buf, &i, "login:")) {
3466               if (!have_sent_ICS_logon) {
3467                 if(ICSInitScript())
3468                   have_sent_ICS_logon = 1;
3469                 else // no init script was found
3470                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3471               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3472                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3473               }
3474                 continue;
3475             }
3476
3477             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3478                 (looking_at(buf, &i, "\n<12> ") ||
3479                  looking_at(buf, &i, "<12> "))) {
3480                 loggedOn = TRUE;
3481                 if (oldi > next_out) {
3482                     SendToPlayer(&buf[next_out], oldi - next_out);
3483                 }
3484                 next_out = i;
3485                 started = STARTED_BOARD;
3486                 parse_pos = 0;
3487                 continue;
3488             }
3489
3490             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3491                 looking_at(buf, &i, "<b1> ")) {
3492                 if (oldi > next_out) {
3493                     SendToPlayer(&buf[next_out], oldi - next_out);
3494                 }
3495                 next_out = i;
3496                 started = STARTED_HOLDINGS;
3497                 parse_pos = 0;
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3502                 loggedOn = TRUE;
3503                 /* Header for a move list -- first line */
3504
3505                 switch (ics_getting_history) {
3506                   case H_FALSE:
3507                     switch (gameMode) {
3508                       case IcsIdle:
3509                       case BeginningOfGame:
3510                         /* User typed "moves" or "oldmoves" while we
3511                            were idle.  Pretend we asked for these
3512                            moves and soak them up so user can step
3513                            through them and/or save them.
3514                            */
3515                         Reset(FALSE, TRUE);
3516                         gameMode = IcsObserving;
3517                         ModeHighlight();
3518                         ics_gamenum = -1;
3519                         ics_getting_history = H_GOT_UNREQ_HEADER;
3520                         break;
3521                       case EditGame: /*?*/
3522                       case EditPosition: /*?*/
3523                         /* Should above feature work in these modes too? */
3524                         /* For now it doesn't */
3525                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3526                         break;
3527                       default:
3528                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3529                         break;
3530                     }
3531                     break;
3532                   case H_REQUESTED:
3533                     /* Is this the right one? */
3534                     if (gameInfo.white && gameInfo.black &&
3535                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3536                         strcmp(gameInfo.black, star_match[2]) == 0) {
3537                         /* All is well */
3538                         ics_getting_history = H_GOT_REQ_HEADER;
3539                     }
3540                     break;
3541                   case H_GOT_REQ_HEADER:
3542                   case H_GOT_UNREQ_HEADER:
3543                   case H_GOT_UNWANTED_HEADER:
3544                   case H_GETTING_MOVES:
3545                     /* Should not happen */
3546                     DisplayError(_("Error gathering move list: two headers"), 0);
3547                     ics_getting_history = H_FALSE;
3548                     break;
3549                 }
3550
3551                 /* Save player ratings into gameInfo if needed */
3552                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3553                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3554                     (gameInfo.whiteRating == -1 ||
3555                      gameInfo.blackRating == -1)) {
3556
3557                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3558                     gameInfo.blackRating = string_to_rating(star_match[3]);
3559                     if (appData.debugMode)
3560                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3561                               gameInfo.whiteRating, gameInfo.blackRating);
3562                 }
3563                 continue;
3564             }
3565
3566             if (looking_at(buf, &i,
3567               "* * match, initial time: * minute*, increment: * second")) {
3568                 /* Header for a move list -- second line */
3569                 /* Initial board will follow if this is a wild game */
3570                 if (gameInfo.event != NULL) free(gameInfo.event);
3571                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3572                 gameInfo.event = StrSave(str);
3573                 /* [HGM] we switched variant. Translate boards if needed. */
3574                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "Move  ")) {
3579                 /* Beginning of a move list */
3580                 switch (ics_getting_history) {
3581                   case H_FALSE:
3582                     /* Normally should not happen */
3583                     /* Maybe user hit reset while we were parsing */
3584                     break;
3585                   case H_REQUESTED:
3586                     /* Happens if we are ignoring a move list that is not
3587                      * the one we just requested.  Common if the user
3588                      * tries to observe two games without turning off
3589                      * getMoveList */
3590                     break;
3591                   case H_GETTING_MOVES:
3592                     /* Should not happen */
3593                     DisplayError(_("Error gathering move list: nested"), 0);
3594                     ics_getting_history = H_FALSE;
3595                     break;
3596                   case H_GOT_REQ_HEADER:
3597                     ics_getting_history = H_GETTING_MOVES;
3598                     started = STARTED_MOVES;
3599                     parse_pos = 0;
3600                     if (oldi > next_out) {
3601                         SendToPlayer(&buf[next_out], oldi - next_out);
3602                     }
3603                     break;
3604                   case H_GOT_UNREQ_HEADER:
3605                     ics_getting_history = H_GETTING_MOVES;
3606                     started = STARTED_MOVES_NOHIDE;
3607                     parse_pos = 0;
3608                     break;
3609                   case H_GOT_UNWANTED_HEADER:
3610                     ics_getting_history = H_FALSE;
3611                     break;
3612                 }
3613                 continue;
3614             }
3615
3616             if (looking_at(buf, &i, "% ") ||
3617                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3618                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3619                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3620                     soughtPending = FALSE;
3621                     seekGraphUp = TRUE;
3622                     DrawSeekGraph();
3623                 }
3624                 if(suppressKibitz) next_out = i;
3625                 savingComment = FALSE;
3626                 suppressKibitz = 0;
3627                 switch (started) {
3628                   case STARTED_MOVES:
3629                   case STARTED_MOVES_NOHIDE:
3630                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3631                     parse[parse_pos + i - oldi] = NULLCHAR;
3632                     ParseGameHistory(parse);
3633 #if ZIPPY
3634                     if (appData.zippyPlay && first.initDone) {
3635                         FeedMovesToProgram(&first, forwardMostMove);
3636                         if (gameMode == IcsPlayingWhite) {
3637                             if (WhiteOnMove(forwardMostMove)) {
3638                                 if (first.sendTime) {
3639                                   if (first.useColors) {
3640                                     SendToProgram("black\n", &first);
3641                                   }
3642                                   SendTimeRemaining(&first, TRUE);
3643                                 }
3644                                 if (first.useColors) {
3645                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3646                                 }
3647                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3648                                 first.maybeThinking = TRUE;
3649                             } else {
3650                                 if (first.usePlayother) {
3651                                   if (first.sendTime) {
3652                                     SendTimeRemaining(&first, TRUE);
3653                                   }
3654                                   SendToProgram("playother\n", &first);
3655                                   firstMove = FALSE;
3656                                 } else {
3657                                   firstMove = TRUE;
3658                                 }
3659                             }
3660                         } else if (gameMode == IcsPlayingBlack) {
3661                             if (!WhiteOnMove(forwardMostMove)) {
3662                                 if (first.sendTime) {
3663                                   if (first.useColors) {
3664                                     SendToProgram("white\n", &first);
3665                                   }
3666                                   SendTimeRemaining(&first, FALSE);
3667                                 }
3668                                 if (first.useColors) {
3669                                   SendToProgram("black\n", &first);
3670                                 }
3671                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3672                                 first.maybeThinking = TRUE;
3673                             } else {
3674                                 if (first.usePlayother) {
3675                                   if (first.sendTime) {
3676                                     SendTimeRemaining(&first, FALSE);
3677                                   }
3678                                   SendToProgram("playother\n", &first);
3679                                   firstMove = FALSE;
3680                                 } else {
3681                                   firstMove = TRUE;
3682                                 }
3683                             }
3684                         }
3685                     }
3686 #endif
3687                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3688                         /* Moves came from oldmoves or moves command
3689                            while we weren't doing anything else.
3690                            */
3691                         currentMove = forwardMostMove;
3692                         ClearHighlights();/*!!could figure this out*/
3693                         flipView = appData.flipView;
3694                         DrawPosition(TRUE, boards[currentMove]);
3695                         DisplayBothClocks();
3696                         snprintf(str, MSG_SIZ, "%s %s %s",
3697                                 gameInfo.white, _("vs."),  gameInfo.black);
3698                         DisplayTitle(str);
3699                         gameMode = IcsIdle;
3700                     } else {
3701                         /* Moves were history of an active game */
3702                         if (gameInfo.resultDetails != NULL) {
3703                             free(gameInfo.resultDetails);
3704                             gameInfo.resultDetails = NULL;
3705                         }
3706                     }
3707                     HistorySet(parseList, backwardMostMove,
3708                                forwardMostMove, currentMove-1);
3709                     DisplayMove(currentMove - 1);
3710                     if (started == STARTED_MOVES) next_out = i;
3711                     started = STARTED_NONE;
3712                     ics_getting_history = H_FALSE;
3713                     break;
3714
3715                   case STARTED_OBSERVE:
3716                     started = STARTED_NONE;
3717                     SendToICS(ics_prefix);
3718                     SendToICS("refresh\n");
3719                     break;
3720
3721                   default:
3722                     break;
3723                 }
3724                 if(bookHit) { // [HGM] book: simulate book reply
3725                     static char bookMove[MSG_SIZ]; // a bit generous?
3726
3727                     programStats.nodes = programStats.depth = programStats.time =
3728                     programStats.score = programStats.got_only_move = 0;
3729                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3730
3731                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3732                     strcat(bookMove, bookHit);
3733                     HandleMachineMove(bookMove, &first);
3734                 }
3735                 continue;
3736             }
3737
3738             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3739                  started == STARTED_HOLDINGS ||
3740                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3741                 /* Accumulate characters in move list or board */
3742                 parse[parse_pos++] = buf[i];
3743             }
3744
3745             /* Start of game messages.  Mostly we detect start of game
3746                when the first board image arrives.  On some versions
3747                of the ICS, though, we need to do a "refresh" after starting
3748                to observe in order to get the current board right away. */
3749             if (looking_at(buf, &i, "Adding game * to observation list")) {
3750                 started = STARTED_OBSERVE;
3751                 continue;
3752             }
3753
3754             /* Handle auto-observe */
3755             if (appData.autoObserve &&
3756                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3757                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3758                 char *player;
3759                 /* Choose the player that was highlighted, if any. */
3760                 if (star_match[0][0] == '\033' ||
3761                     star_match[1][0] != '\033') {
3762                     player = star_match[0];
3763                 } else {
3764                     player = star_match[2];
3765                 }
3766                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3767                         ics_prefix, StripHighlightAndTitle(player));
3768                 SendToICS(str);
3769
3770                 /* Save ratings from notify string */
3771                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3772                 player1Rating = string_to_rating(star_match[1]);
3773                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3774                 player2Rating = string_to_rating(star_match[3]);
3775
3776                 if (appData.debugMode)
3777                   fprintf(debugFP,
3778                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3779                           player1Name, player1Rating,
3780                           player2Name, player2Rating);
3781
3782                 continue;
3783             }
3784
3785             /* Deal with automatic examine mode after a game,
3786                and with IcsObserving -> IcsExamining transition */
3787             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3788                 looking_at(buf, &i, "has made you an examiner of game *")) {
3789
3790                 int gamenum = atoi(star_match[0]);
3791                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3792                     gamenum == ics_gamenum) {
3793                     /* We were already playing or observing this game;
3794                        no need to refetch history */
3795                     gameMode = IcsExamining;
3796                     if (pausing) {
3797                         pauseExamForwardMostMove = forwardMostMove;
3798                     } else if (currentMove < forwardMostMove) {
3799                         ForwardInner(forwardMostMove);
3800                     }
3801                 } else {
3802                     /* I don't think this case really can happen */
3803                     SendToICS(ics_prefix);
3804                     SendToICS("refresh\n");
3805                 }
3806                 continue;
3807             }
3808
3809             /* Error messages */
3810 //          if (ics_user_moved) {
3811             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3812                 if (looking_at(buf, &i, "Illegal move") ||
3813                     looking_at(buf, &i, "Not a legal move") ||
3814                     looking_at(buf, &i, "Your king is in check") ||
3815                     looking_at(buf, &i, "It isn't your turn") ||
3816                     looking_at(buf, &i, "It is not your move")) {
3817                     /* Illegal move */
3818                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3819                         currentMove = forwardMostMove-1;
3820                         DisplayMove(currentMove - 1); /* before DMError */
3821                         DrawPosition(FALSE, boards[currentMove]);
3822                         SwitchClocks(forwardMostMove-1); // [HGM] race
3823                         DisplayBothClocks();
3824                     }
3825                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3826                     ics_user_moved = 0;
3827                     continue;
3828                 }
3829             }
3830
3831             if (looking_at(buf, &i, "still have time") ||
3832                 looking_at(buf, &i, "not out of time") ||
3833                 looking_at(buf, &i, "either player is out of time") ||
3834                 looking_at(buf, &i, "has timeseal; checking")) {
3835                 /* We must have called his flag a little too soon */
3836                 whiteFlag = blackFlag = FALSE;
3837                 continue;
3838             }
3839
3840             if (looking_at(buf, &i, "added * seconds to") ||
3841                 looking_at(buf, &i, "seconds were added to")) {
3842                 /* Update the clocks */
3843                 SendToICS(ics_prefix);
3844                 SendToICS("refresh\n");
3845                 continue;
3846             }
3847
3848             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3849                 ics_clock_paused = TRUE;
3850                 StopClocks();
3851                 continue;
3852             }
3853
3854             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3855                 ics_clock_paused = FALSE;
3856                 StartClocks();
3857                 continue;
3858             }
3859
3860             /* Grab player ratings from the Creating: message.
3861                Note we have to check for the special case when
3862                the ICS inserts things like [white] or [black]. */
3863             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3864                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3865                 /* star_matches:
3866                    0    player 1 name (not necessarily white)
3867                    1    player 1 rating
3868                    2    empty, white, or black (IGNORED)
3869                    3    player 2 name (not necessarily black)
3870                    4    player 2 rating
3871
3872                    The names/ratings are sorted out when the game
3873                    actually starts (below).
3874                 */
3875                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3876                 player1Rating = string_to_rating(star_match[1]);
3877                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3878                 player2Rating = string_to_rating(star_match[4]);
3879
3880                 if (appData.debugMode)
3881                   fprintf(debugFP,
3882                           "Ratings from 'Creating:' %s %d, %s %d\n",
3883                           player1Name, player1Rating,
3884                           player2Name, player2Rating);
3885
3886                 continue;
3887             }
3888
3889             /* Improved generic start/end-of-game messages */
3890             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3891                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3892                 /* If tkind == 0: */
3893                 /* star_match[0] is the game number */
3894                 /*           [1] is the white player's name */
3895                 /*           [2] is the black player's name */
3896                 /* For end-of-game: */
3897                 /*           [3] is the reason for the game end */
3898                 /*           [4] is a PGN end game-token, preceded by " " */
3899                 /* For start-of-game: */
3900                 /*           [3] begins with "Creating" or "Continuing" */
3901                 /*           [4] is " *" or empty (don't care). */
3902                 int gamenum = atoi(star_match[0]);
3903                 char *whitename, *blackname, *why, *endtoken;
3904                 ChessMove endtype = EndOfFile;
3905
3906                 if (tkind == 0) {
3907                   whitename = star_match[1];
3908                   blackname = star_match[2];
3909                   why = star_match[3];
3910                   endtoken = star_match[4];
3911                 } else {
3912                   whitename = star_match[1];
3913                   blackname = star_match[3];
3914                   why = star_match[5];
3915                   endtoken = star_match[6];
3916                 }
3917
3918                 /* Game start messages */
3919                 if (strncmp(why, "Creating ", 9) == 0 ||
3920                     strncmp(why, "Continuing ", 11) == 0) {
3921                     gs_gamenum = gamenum;
3922                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3923                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3924                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3925 #if ZIPPY
3926                     if (appData.zippyPlay) {
3927                         ZippyGameStart(whitename, blackname);
3928                     }
3929 #endif /*ZIPPY*/
3930                     partnerBoardValid = FALSE; // [HGM] bughouse
3931                     continue;
3932                 }
3933
3934                 /* Game end messages */
3935                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3936                     ics_gamenum != gamenum) {
3937                     continue;
3938                 }
3939                 while (endtoken[0] == ' ') endtoken++;
3940                 switch (endtoken[0]) {
3941                   case '*':
3942                   default:
3943                     endtype = GameUnfinished;
3944                     break;
3945                   case '0':
3946                     endtype = BlackWins;
3947                     break;
3948                   case '1':
3949                     if (endtoken[1] == '/')
3950                       endtype = GameIsDrawn;
3951                     else
3952                       endtype = WhiteWins;
3953                     break;
3954                 }
3955                 GameEnds(endtype, why, GE_ICS);
3956 #if ZIPPY
3957                 if (appData.zippyPlay && first.initDone) {
3958                     ZippyGameEnd(endtype, why);
3959                     if (first.pr == NoProc) {
3960                       /* Start the next process early so that we'll
3961                          be ready for the next challenge */
3962                       StartChessProgram(&first);
3963                     }
3964                     /* Send "new" early, in case this command takes
3965                        a long time to finish, so that we'll be ready
3966                        for the next challenge. */
3967                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3968                     Reset(TRUE, TRUE);
3969                 }
3970 #endif /*ZIPPY*/
3971                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3972                 continue;
3973             }
3974
3975             if (looking_at(buf, &i, "Removing game * from observation") ||
3976                 looking_at(buf, &i, "no longer observing game *") ||
3977                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3978                 if (gameMode == IcsObserving &&
3979                     atoi(star_match[0]) == ics_gamenum)
3980                   {
3981                       /* icsEngineAnalyze */
3982                       if (appData.icsEngineAnalyze) {
3983                             ExitAnalyzeMode();
3984                             ModeHighlight();
3985                       }
3986                       StopClocks();
3987                       gameMode = IcsIdle;
3988                       ics_gamenum = -1;
3989                       ics_user_moved = FALSE;
3990                   }
3991                 continue;
3992             }
3993
3994             if (looking_at(buf, &i, "no longer examining game *")) {
3995                 if (gameMode == IcsExamining &&
3996                     atoi(star_match[0]) == ics_gamenum)
3997                   {
3998                       gameMode = IcsIdle;
3999                       ics_gamenum = -1;
4000                       ics_user_moved = FALSE;
4001                   }
4002                 continue;
4003             }
4004
4005             /* Advance leftover_start past any newlines we find,
4006                so only partial lines can get reparsed */
4007             if (looking_at(buf, &i, "\n")) {
4008                 prevColor = curColor;
4009                 if (curColor != ColorNormal) {
4010                     if (oldi > next_out) {
4011                         SendToPlayer(&buf[next_out], oldi - next_out);
4012                         next_out = oldi;
4013                     }
4014                     Colorize(ColorNormal, FALSE);
4015                     curColor = ColorNormal;
4016                 }
4017                 if (started == STARTED_BOARD) {
4018                     started = STARTED_NONE;
4019                     parse[parse_pos] = NULLCHAR;
4020                     ParseBoard12(parse);
4021                     ics_user_moved = 0;
4022
4023                     /* Send premove here */
4024                     if (appData.premove) {
4025                       char str[MSG_SIZ];
4026                       if (currentMove == 0 &&
4027                           gameMode == IcsPlayingWhite &&
4028                           appData.premoveWhite) {
4029                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4030                         if (appData.debugMode)
4031                           fprintf(debugFP, "Sending premove:\n");
4032                         SendToICS(str);
4033                       } else if (currentMove == 1 &&
4034                                  gameMode == IcsPlayingBlack &&
4035                                  appData.premoveBlack) {
4036                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4037                         if (appData.debugMode)
4038                           fprintf(debugFP, "Sending premove:\n");
4039                         SendToICS(str);
4040                       } else if (gotPremove) {
4041                         gotPremove = 0;
4042                         ClearPremoveHighlights();
4043                         if (appData.debugMode)
4044                           fprintf(debugFP, "Sending premove:\n");
4045                           UserMoveEvent(premoveFromX, premoveFromY,
4046                                         premoveToX, premoveToY,
4047                                         premovePromoChar);
4048                       }
4049                     }
4050
4051                     /* Usually suppress following prompt */
4052                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4053                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4054                         if (looking_at(buf, &i, "*% ")) {
4055                             savingComment = FALSE;
4056                             suppressKibitz = 0;
4057                         }
4058                     }
4059                     next_out = i;
4060                 } else if (started == STARTED_HOLDINGS) {
4061                     int gamenum;
4062                     char new_piece[MSG_SIZ];
4063                     started = STARTED_NONE;
4064                     parse[parse_pos] = NULLCHAR;
4065                     if (appData.debugMode)
4066                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4067                                                         parse, currentMove);
4068                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4069                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4070                         if (gameInfo.variant == VariantNormal) {
4071                           /* [HGM] We seem to switch variant during a game!
4072                            * Presumably no holdings were displayed, so we have
4073                            * to move the position two files to the right to
4074                            * create room for them!
4075                            */
4076                           VariantClass newVariant;
4077                           switch(gameInfo.boardWidth) { // base guess on board width
4078                                 case 9:  newVariant = VariantShogi; break;
4079                                 case 10: newVariant = VariantGreat; break;
4080                                 default: newVariant = VariantCrazyhouse; break;
4081                           }
4082                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4083                           /* Get a move list just to see the header, which
4084                              will tell us whether this is really bug or zh */
4085                           if (ics_getting_history == H_FALSE) {
4086                             ics_getting_history = H_REQUESTED;
4087                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4088                             SendToICS(str);
4089                           }
4090                         }
4091                         new_piece[0] = NULLCHAR;
4092                         sscanf(parse, "game %d white [%s black [%s <- %s",
4093                                &gamenum, white_holding, black_holding,
4094                                new_piece);
4095                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4096                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4097                         /* [HGM] copy holdings to board holdings area */
4098                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4099                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4100                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4101 #if ZIPPY
4102                         if (appData.zippyPlay && first.initDone) {
4103                             ZippyHoldings(white_holding, black_holding,
4104                                           new_piece);
4105                         }
4106 #endif /*ZIPPY*/
4107                         if (tinyLayout || smallLayout) {
4108                             char wh[16], bh[16];
4109                             PackHolding(wh, white_holding);
4110                             PackHolding(bh, black_holding);
4111                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4112                                     gameInfo.white, gameInfo.black);
4113                         } else {
4114                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4115                                     gameInfo.white, white_holding, _("vs."),
4116                                     gameInfo.black, black_holding);
4117                         }
4118                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4119                         DrawPosition(FALSE, boards[currentMove]);
4120                         DisplayTitle(str);
4121                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4122                         sscanf(parse, "game %d white [%s black [%s <- %s",
4123                                &gamenum, white_holding, black_holding,
4124                                new_piece);
4125                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4126                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4127                         /* [HGM] copy holdings to partner-board holdings area */
4128                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4129                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4130                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4131                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4132                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4133                       }
4134                     }
4135                     /* Suppress following prompt */
4136                     if (looking_at(buf, &i, "*% ")) {
4137                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4138                         savingComment = FALSE;
4139                         suppressKibitz = 0;
4140                     }
4141                     next_out = i;
4142                 }
4143                 continue;
4144             }
4145
4146             i++;                /* skip unparsed character and loop back */
4147         }
4148
4149         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4150 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4151 //          SendToPlayer(&buf[next_out], i - next_out);
4152             started != STARTED_HOLDINGS && leftover_start > next_out) {
4153             SendToPlayer(&buf[next_out], leftover_start - next_out);
4154             next_out = i;
4155         }
4156
4157         leftover_len = buf_len - leftover_start;
4158         /* if buffer ends with something we couldn't parse,
4159            reparse it after appending the next read */
4160
4161     } else if (count == 0) {
4162         RemoveInputSource(isr);
4163         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4164     } else {
4165         DisplayFatalError(_("Error reading from ICS"), error, 1);
4166     }
4167 }
4168
4169
4170 /* Board style 12 looks like this:
4171
4172    <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
4173
4174  * The "<12> " is stripped before it gets to this routine.  The two
4175  * trailing 0's (flip state and clock ticking) are later addition, and
4176  * some chess servers may not have them, or may have only the first.
4177  * Additional trailing fields may be added in the future.
4178  */
4179
4180 #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"
4181
4182 #define RELATION_OBSERVING_PLAYED    0
4183 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4184 #define RELATION_PLAYING_MYMOVE      1
4185 #define RELATION_PLAYING_NOTMYMOVE  -1
4186 #define RELATION_EXAMINING           2
4187 #define RELATION_ISOLATED_BOARD     -3
4188 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4189
4190 void
4191 ParseBoard12 (char *string)
4192 {
4193 #if ZIPPY
4194     int i, takeback;
4195     char *bookHit = NULL; // [HGM] book
4196 #endif
4197     GameMode newGameMode;
4198     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4199     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4200     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4201     char to_play, board_chars[200];
4202     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4203     char black[32], white[32];
4204     Board board;
4205     int prevMove = currentMove;
4206     int ticking = 2;
4207     ChessMove moveType;
4208     int fromX, fromY, toX, toY;
4209     char promoChar;
4210     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4211     Boolean weird = FALSE, reqFlag = FALSE;
4212
4213     fromX = fromY = toX = toY = -1;
4214
4215     newGame = FALSE;
4216
4217     if (appData.debugMode)
4218       fprintf(debugFP, "Parsing board: %s\n", string);
4219
4220     move_str[0] = NULLCHAR;
4221     elapsed_time[0] = NULLCHAR;
4222     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4223         int  i = 0, j;
4224         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4225             if(string[i] == ' ') { ranks++; files = 0; }
4226             else files++;
4227             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4228             i++;
4229         }
4230         for(j = 0; j <i; j++) board_chars[j] = string[j];
4231         board_chars[i] = '\0';
4232         string += i + 1;
4233     }
4234     n = sscanf(string, PATTERN, &to_play, &double_push,
4235                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4236                &gamenum, white, black, &relation, &basetime, &increment,
4237                &white_stren, &black_stren, &white_time, &black_time,
4238                &moveNum, str, elapsed_time, move_str, &ics_flip,
4239                &ticking);
4240
4241     if (n < 21) {
4242         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4243         DisplayError(str, 0);
4244         return;
4245     }
4246
4247     /* Convert the move number to internal form */
4248     moveNum = (moveNum - 1) * 2;
4249     if (to_play == 'B') moveNum++;
4250     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4251       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4252                         0, 1);
4253       return;
4254     }
4255
4256     switch (relation) {
4257       case RELATION_OBSERVING_PLAYED:
4258       case RELATION_OBSERVING_STATIC:
4259         if (gamenum == -1) {
4260             /* Old ICC buglet */
4261             relation = RELATION_OBSERVING_STATIC;
4262         }
4263         newGameMode = IcsObserving;
4264         break;
4265       case RELATION_PLAYING_MYMOVE:
4266       case RELATION_PLAYING_NOTMYMOVE:
4267         newGameMode =
4268           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4269             IcsPlayingWhite : IcsPlayingBlack;
4270         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4271         break;
4272       case RELATION_EXAMINING:
4273         newGameMode = IcsExamining;
4274         break;
4275       case RELATION_ISOLATED_BOARD:
4276       default:
4277         /* Just display this board.  If user was doing something else,
4278            we will forget about it until the next board comes. */
4279         newGameMode = IcsIdle;
4280         break;
4281       case RELATION_STARTING_POSITION:
4282         newGameMode = gameMode;
4283         break;
4284     }
4285
4286     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4287         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4288          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4289       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4290       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4291       static int lastBgGame = -1;
4292       char *toSqr;
4293       for (k = 0; k < ranks; k++) {
4294         for (j = 0; j < files; j++)
4295           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4296         if(gameInfo.holdingsWidth > 1) {
4297              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4298              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4299         }
4300       }
4301       CopyBoard(partnerBoard, board);
4302       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4303         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4304         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4305       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4306       if(toSqr = strchr(str, '-')) {
4307         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4308         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4309       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4310       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4311       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4312       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4313       if(twoBoards) {
4314           DisplayWhiteClock(white_time*fac, to_play == 'W');
4315           DisplayBlackClock(black_time*fac, to_play != 'W');
4316           activePartner = to_play;
4317           if(gamenum != lastBgGame) {
4318               char buf[MSG_SIZ];
4319               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4320               DisplayTitle(buf);
4321           }
4322           lastBgGame = gamenum;
4323           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4324                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4325       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4326                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4327       if(!twoBoards) DisplayMessage(partnerStatus, "");
4328         partnerBoardValid = TRUE;
4329       return;
4330     }
4331
4332     if(appData.dualBoard && appData.bgObserve) {
4333         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4334             SendToICS(ics_prefix), SendToICS("pobserve\n");
4335         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4336             char buf[MSG_SIZ];
4337             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4338             SendToICS(buf);
4339         }
4340     }
4341
4342     /* Modify behavior for initial board display on move listing
4343        of wild games.
4344        */
4345     switch (ics_getting_history) {
4346       case H_FALSE:
4347       case H_REQUESTED:
4348         break;
4349       case H_GOT_REQ_HEADER:
4350       case H_GOT_UNREQ_HEADER:
4351         /* This is the initial position of the current game */
4352         gamenum = ics_gamenum;
4353         moveNum = 0;            /* old ICS bug workaround */
4354         if (to_play == 'B') {
4355           startedFromSetupPosition = TRUE;
4356           blackPlaysFirst = TRUE;
4357           moveNum = 1;
4358           if (forwardMostMove == 0) forwardMostMove = 1;
4359           if (backwardMostMove == 0) backwardMostMove = 1;
4360           if (currentMove == 0) currentMove = 1;
4361         }
4362         newGameMode = gameMode;
4363         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4364         break;
4365       case H_GOT_UNWANTED_HEADER:
4366         /* This is an initial board that we don't want */
4367         return;
4368       case H_GETTING_MOVES:
4369         /* Should not happen */
4370         DisplayError(_("Error gathering move list: extra board"), 0);
4371         ics_getting_history = H_FALSE;
4372         return;
4373     }
4374
4375    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4376                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4377                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4378      /* [HGM] We seem to have switched variant unexpectedly
4379       * Try to guess new variant from board size
4380       */
4381           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4382           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4383           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4384           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4385           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4386           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4387           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4388           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4389           /* Get a move list just to see the header, which
4390              will tell us whether this is really bug or zh */
4391           if (ics_getting_history == H_FALSE) {
4392             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4393             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4394             SendToICS(str);
4395           }
4396     }
4397
4398     /* Take action if this is the first board of a new game, or of a
4399        different game than is currently being displayed.  */
4400     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4401         relation == RELATION_ISOLATED_BOARD) {
4402
4403         /* Forget the old game and get the history (if any) of the new one */
4404         if (gameMode != BeginningOfGame) {
4405           Reset(TRUE, TRUE);
4406         }
4407         newGame = TRUE;
4408         if (appData.autoRaiseBoard) BoardToTop();
4409         prevMove = -3;
4410         if (gamenum == -1) {
4411             newGameMode = IcsIdle;
4412         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4413                    appData.getMoveList && !reqFlag) {
4414             /* Need to get game history */
4415             ics_getting_history = H_REQUESTED;
4416             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4417             SendToICS(str);
4418         }
4419
4420         /* Initially flip the board to have black on the bottom if playing
4421            black or if the ICS flip flag is set, but let the user change
4422            it with the Flip View button. */
4423         flipView = appData.autoFlipView ?
4424           (newGameMode == IcsPlayingBlack) || ics_flip :
4425           appData.flipView;
4426
4427         /* Done with values from previous mode; copy in new ones */
4428         gameMode = newGameMode;
4429         ModeHighlight();
4430         ics_gamenum = gamenum;
4431         if (gamenum == gs_gamenum) {
4432             int klen = strlen(gs_kind);
4433             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4434             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4435             gameInfo.event = StrSave(str);
4436         } else {
4437             gameInfo.event = StrSave("ICS game");
4438         }
4439         gameInfo.site = StrSave(appData.icsHost);
4440         gameInfo.date = PGNDate();
4441         gameInfo.round = StrSave("-");
4442         gameInfo.white = StrSave(white);
4443         gameInfo.black = StrSave(black);
4444         timeControl = basetime * 60 * 1000;
4445         timeControl_2 = 0;
4446         timeIncrement = increment * 1000;
4447         movesPerSession = 0;
4448         gameInfo.timeControl = TimeControlTagValue();
4449         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4450   if (appData.debugMode) {
4451     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4452     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4453     setbuf(debugFP, NULL);
4454   }
4455
4456         gameInfo.outOfBook = NULL;
4457
4458         /* Do we have the ratings? */
4459         if (strcmp(player1Name, white) == 0 &&
4460             strcmp(player2Name, black) == 0) {
4461             if (appData.debugMode)
4462               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4463                       player1Rating, player2Rating);
4464             gameInfo.whiteRating = player1Rating;
4465             gameInfo.blackRating = player2Rating;
4466         } else if (strcmp(player2Name, white) == 0 &&
4467                    strcmp(player1Name, black) == 0) {
4468             if (appData.debugMode)
4469               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4470                       player2Rating, player1Rating);
4471             gameInfo.whiteRating = player2Rating;
4472             gameInfo.blackRating = player1Rating;
4473         }
4474         player1Name[0] = player2Name[0] = NULLCHAR;
4475
4476         /* Silence shouts if requested */
4477         if (appData.quietPlay &&
4478             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4479             SendToICS(ics_prefix);
4480             SendToICS("set shout 0\n");
4481         }
4482     }
4483
4484     /* Deal with midgame name changes */
4485     if (!newGame) {
4486         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4487             if (gameInfo.white) free(gameInfo.white);
4488             gameInfo.white = StrSave(white);
4489         }
4490         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4491             if (gameInfo.black) free(gameInfo.black);
4492             gameInfo.black = StrSave(black);
4493         }
4494     }
4495
4496     /* Throw away game result if anything actually changes in examine mode */
4497     if (gameMode == IcsExamining && !newGame) {
4498         gameInfo.result = GameUnfinished;
4499         if (gameInfo.resultDetails != NULL) {
4500             free(gameInfo.resultDetails);
4501             gameInfo.resultDetails = NULL;
4502         }
4503     }
4504
4505     /* In pausing && IcsExamining mode, we ignore boards coming
4506        in if they are in a different variation than we are. */
4507     if (pauseExamInvalid) return;
4508     if (pausing && gameMode == IcsExamining) {
4509         if (moveNum <= pauseExamForwardMostMove) {
4510             pauseExamInvalid = TRUE;
4511             forwardMostMove = pauseExamForwardMostMove;
4512             return;
4513         }
4514     }
4515
4516   if (appData.debugMode) {
4517     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4518   }
4519     /* Parse the board */
4520     for (k = 0; k < ranks; k++) {
4521       for (j = 0; j < files; j++)
4522         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4523       if(gameInfo.holdingsWidth > 1) {
4524            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4525            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4526       }
4527     }
4528     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4529       board[5][BOARD_RGHT+1] = WhiteAngel;
4530       board[6][BOARD_RGHT+1] = WhiteMarshall;
4531       board[1][0] = BlackMarshall;
4532       board[2][0] = BlackAngel;
4533       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4534     }
4535     CopyBoard(boards[moveNum], board);
4536     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4537     if (moveNum == 0) {
4538         startedFromSetupPosition =
4539           !CompareBoards(board, initialPosition);
4540         if(startedFromSetupPosition)
4541             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4542     }
4543
4544     /* [HGM] Set castling rights. Take the outermost Rooks,
4545        to make it also work for FRC opening positions. Note that board12
4546        is really defective for later FRC positions, as it has no way to
4547        indicate which Rook can castle if they are on the same side of King.
4548        For the initial position we grant rights to the outermost Rooks,
4549        and remember thos rights, and we then copy them on positions
4550        later in an FRC game. This means WB might not recognize castlings with
4551        Rooks that have moved back to their original position as illegal,
4552        but in ICS mode that is not its job anyway.
4553     */
4554     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4555     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4556
4557         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4558             if(board[0][i] == WhiteRook) j = i;
4559         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4560         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4561             if(board[0][i] == WhiteRook) j = i;
4562         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4563         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4564             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4565         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4566         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4567             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4568         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4569
4570         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4571         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4572         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4573             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4574         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4575             if(board[BOARD_HEIGHT-1][k] == bKing)
4576                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4577         if(gameInfo.variant == VariantTwoKings) {
4578             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4579             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4580             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4581         }
4582     } else { int r;
4583         r = boards[moveNum][CASTLING][0] = initialRights[0];
4584         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4585         r = boards[moveNum][CASTLING][1] = initialRights[1];
4586         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4587         r = boards[moveNum][CASTLING][3] = initialRights[3];
4588         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4589         r = boards[moveNum][CASTLING][4] = initialRights[4];
4590         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4591         /* wildcastle kludge: always assume King has rights */
4592         r = boards[moveNum][CASTLING][2] = initialRights[2];
4593         r = boards[moveNum][CASTLING][5] = initialRights[5];
4594     }
4595     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4596     boards[moveNum][EP_STATUS] = EP_NONE;
4597     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4598     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4599     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4600
4601
4602     if (ics_getting_history == H_GOT_REQ_HEADER ||
4603         ics_getting_history == H_GOT_UNREQ_HEADER) {
4604         /* This was an initial position from a move list, not
4605            the current position */
4606         return;
4607     }
4608
4609     /* Update currentMove and known move number limits */
4610     newMove = newGame || moveNum > forwardMostMove;
4611
4612     if (newGame) {
4613         forwardMostMove = backwardMostMove = currentMove = moveNum;
4614         if (gameMode == IcsExamining && moveNum == 0) {
4615           /* Workaround for ICS limitation: we are not told the wild
4616              type when starting to examine a game.  But if we ask for
4617              the move list, the move list header will tell us */
4618             ics_getting_history = H_REQUESTED;
4619             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4620             SendToICS(str);
4621         }
4622     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4623                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4624 #if ZIPPY
4625         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4626         /* [HGM] applied this also to an engine that is silently watching        */
4627         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4628             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4629             gameInfo.variant == currentlyInitializedVariant) {
4630           takeback = forwardMostMove - moveNum;
4631           for (i = 0; i < takeback; i++) {
4632             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4633             SendToProgram("undo\n", &first);
4634           }
4635         }
4636 #endif
4637
4638         forwardMostMove = moveNum;
4639         if (!pausing || currentMove > forwardMostMove)
4640           currentMove = forwardMostMove;
4641     } else {
4642         /* New part of history that is not contiguous with old part */
4643         if (pausing && gameMode == IcsExamining) {
4644             pauseExamInvalid = TRUE;
4645             forwardMostMove = pauseExamForwardMostMove;
4646             return;
4647         }
4648         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4649 #if ZIPPY
4650             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4651                 // [HGM] when we will receive the move list we now request, it will be
4652                 // fed to the engine from the first move on. So if the engine is not
4653                 // in the initial position now, bring it there.
4654                 InitChessProgram(&first, 0);
4655             }
4656 #endif
4657             ics_getting_history = H_REQUESTED;
4658             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4659             SendToICS(str);
4660         }
4661         forwardMostMove = backwardMostMove = currentMove = moveNum;
4662     }
4663
4664     /* Update the clocks */
4665     if (strchr(elapsed_time, '.')) {
4666       /* Time is in ms */
4667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4669     } else {
4670       /* Time is in seconds */
4671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4673     }
4674
4675
4676 #if ZIPPY
4677     if (appData.zippyPlay && newGame &&
4678         gameMode != IcsObserving && gameMode != IcsIdle &&
4679         gameMode != IcsExamining)
4680       ZippyFirstBoard(moveNum, basetime, increment);
4681 #endif
4682
4683     /* Put the move on the move list, first converting
4684        to canonical algebraic form. */
4685     if (moveNum > 0) {
4686   if (appData.debugMode) {
4687     int f = forwardMostMove;
4688     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4689             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4690             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4691     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4692     fprintf(debugFP, "moveNum = %d\n", moveNum);
4693     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4694     setbuf(debugFP, NULL);
4695   }
4696         if (moveNum <= backwardMostMove) {
4697             /* We don't know what the board looked like before
4698                this move.  Punt. */
4699           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4700             strcat(parseList[moveNum - 1], " ");
4701             strcat(parseList[moveNum - 1], elapsed_time);
4702             moveList[moveNum - 1][0] = NULLCHAR;
4703         } else if (strcmp(move_str, "none") == 0) {
4704             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4705             /* Again, we don't know what the board looked like;
4706                this is really the start of the game. */
4707             parseList[moveNum - 1][0] = NULLCHAR;
4708             moveList[moveNum - 1][0] = NULLCHAR;
4709             backwardMostMove = moveNum;
4710             startedFromSetupPosition = TRUE;
4711             fromX = fromY = toX = toY = -1;
4712         } else {
4713           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4714           //                 So we parse the long-algebraic move string in stead of the SAN move
4715           int valid; char buf[MSG_SIZ], *prom;
4716
4717           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4718                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4719           // str looks something like "Q/a1-a2"; kill the slash
4720           if(str[1] == '/')
4721             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4722           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4723           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4724                 strcat(buf, prom); // long move lacks promo specification!
4725           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4726                 if(appData.debugMode)
4727                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4728                 safeStrCpy(move_str, buf, MSG_SIZ);
4729           }
4730           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4731                                 &fromX, &fromY, &toX, &toY, &promoChar)
4732                || ParseOneMove(buf, moveNum - 1, &moveType,
4733                                 &fromX, &fromY, &toX, &toY, &promoChar);
4734           // end of long SAN patch
4735           if (valid) {
4736             (void) CoordsToAlgebraic(boards[moveNum - 1],
4737                                      PosFlags(moveNum - 1),
4738                                      fromY, fromX, toY, toX, promoChar,
4739                                      parseList[moveNum-1]);
4740             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4741               case MT_NONE:
4742               case MT_STALEMATE:
4743               default:
4744                 break;
4745               case MT_CHECK:
4746                 if(gameInfo.variant != VariantShogi)
4747                     strcat(parseList[moveNum - 1], "+");
4748                 break;
4749               case MT_CHECKMATE:
4750               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4751                 strcat(parseList[moveNum - 1], "#");
4752                 break;
4753             }
4754             strcat(parseList[moveNum - 1], " ");
4755             strcat(parseList[moveNum - 1], elapsed_time);
4756             /* currentMoveString is set as a side-effect of ParseOneMove */
4757             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4758             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4759             strcat(moveList[moveNum - 1], "\n");
4760
4761             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4762                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4763               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4764                 ChessSquare old, new = boards[moveNum][k][j];
4765                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4766                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4767                   if(old == new) continue;
4768                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4769                   else if(new == WhiteWazir || new == BlackWazir) {
4770                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4771                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4772                       else boards[moveNum][k][j] = old; // preserve type of Gold
4773                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4774                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4775               }
4776           } else {
4777             /* Move from ICS was illegal!?  Punt. */
4778             if (appData.debugMode) {
4779               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4780               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4781             }
4782             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4783             strcat(parseList[moveNum - 1], " ");
4784             strcat(parseList[moveNum - 1], elapsed_time);
4785             moveList[moveNum - 1][0] = NULLCHAR;
4786             fromX = fromY = toX = toY = -1;
4787           }
4788         }
4789   if (appData.debugMode) {
4790     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4791     setbuf(debugFP, NULL);
4792   }
4793
4794 #if ZIPPY
4795         /* Send move to chess program (BEFORE animating it). */
4796         if (appData.zippyPlay && !newGame && newMove &&
4797            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4798
4799             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4800                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4801                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4802                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4803                             move_str);
4804                     DisplayError(str, 0);
4805                 } else {
4806                     if (first.sendTime) {
4807                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4808                     }
4809                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4810                     if (firstMove && !bookHit) {
4811                         firstMove = FALSE;
4812                         if (first.useColors) {
4813                           SendToProgram(gameMode == IcsPlayingWhite ?
4814                                         "white\ngo\n" :
4815                                         "black\ngo\n", &first);
4816                         } else {
4817                           SendToProgram("go\n", &first);
4818                         }
4819                         first.maybeThinking = TRUE;
4820                     }
4821                 }
4822             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4823               if (moveList[moveNum - 1][0] == NULLCHAR) {
4824                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4825                 DisplayError(str, 0);
4826               } else {
4827                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4828                 SendMoveToProgram(moveNum - 1, &first);
4829               }
4830             }
4831         }
4832 #endif
4833     }
4834
4835     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4836         /* If move comes from a remote source, animate it.  If it
4837            isn't remote, it will have already been animated. */
4838         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4839             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4840         }
4841         if (!pausing && appData.highlightLastMove) {
4842             SetHighlights(fromX, fromY, toX, toY);
4843         }
4844     }
4845
4846     /* Start the clocks */
4847     whiteFlag = blackFlag = FALSE;
4848     appData.clockMode = !(basetime == 0 && increment == 0);
4849     if (ticking == 0) {
4850       ics_clock_paused = TRUE;
4851       StopClocks();
4852     } else if (ticking == 1) {
4853       ics_clock_paused = FALSE;
4854     }
4855     if (gameMode == IcsIdle ||
4856         relation == RELATION_OBSERVING_STATIC ||
4857         relation == RELATION_EXAMINING ||
4858         ics_clock_paused)
4859       DisplayBothClocks();
4860     else
4861       StartClocks();
4862
4863     /* Display opponents and material strengths */
4864     if (gameInfo.variant != VariantBughouse &&
4865         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4866         if (tinyLayout || smallLayout) {
4867             if(gameInfo.variant == VariantNormal)
4868               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4869                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4870                     basetime, increment);
4871             else
4872               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4873                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4874                     basetime, increment, (int) gameInfo.variant);
4875         } else {
4876             if(gameInfo.variant == VariantNormal)
4877               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4878                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4879                     basetime, increment);
4880             else
4881               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4882                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4883                     basetime, increment, VariantName(gameInfo.variant));
4884         }
4885         DisplayTitle(str);
4886   if (appData.debugMode) {
4887     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4888   }
4889     }
4890
4891
4892     /* Display the board */
4893     if (!pausing && !appData.noGUI) {
4894
4895       if (appData.premove)
4896           if (!gotPremove ||
4897              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4898              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4899               ClearPremoveHighlights();
4900
4901       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4902         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4903       DrawPosition(j, boards[currentMove]);
4904
4905       DisplayMove(moveNum - 1);
4906       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4907             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4908               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4909         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4910       }
4911     }
4912
4913     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4914 #if ZIPPY
4915     if(bookHit) { // [HGM] book: simulate book reply
4916         static char bookMove[MSG_SIZ]; // a bit generous?
4917
4918         programStats.nodes = programStats.depth = programStats.time =
4919         programStats.score = programStats.got_only_move = 0;
4920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4921
4922         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4923         strcat(bookMove, bookHit);
4924         HandleMachineMove(bookMove, &first);
4925     }
4926 #endif
4927 }
4928
4929 void
4930 GetMoveListEvent ()
4931 {
4932     char buf[MSG_SIZ];
4933     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4934         ics_getting_history = H_REQUESTED;
4935         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4936         SendToICS(buf);
4937     }
4938 }
4939
4940 void
4941 SendToBoth (char *msg)
4942 {   // to make it easy to keep two engines in step in dual analysis
4943     SendToProgram(msg, &first);
4944     if(second.analyzing) SendToProgram(msg, &second);
4945 }
4946
4947 void
4948 AnalysisPeriodicEvent (int force)
4949 {
4950     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4951          && !force) || !appData.periodicUpdates)
4952       return;
4953
4954     /* Send . command to Crafty to collect stats */
4955     SendToBoth(".\n");
4956
4957     /* Don't send another until we get a response (this makes
4958        us stop sending to old Crafty's which don't understand
4959        the "." command (sending illegal cmds resets node count & time,
4960        which looks bad)) */
4961     programStats.ok_to_send = 0;
4962 }
4963
4964 void
4965 ics_update_width (int new_width)
4966 {
4967         ics_printf("set width %d\n", new_width);
4968 }
4969
4970 void
4971 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4972 {
4973     char buf[MSG_SIZ];
4974
4975     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4976         // null move in variant where engine does not understand it (for analysis purposes)
4977         SendBoard(cps, moveNum + 1); // send position after move in stead.
4978         return;
4979     }
4980     if (cps->useUsermove) {
4981       SendToProgram("usermove ", cps);
4982     }
4983     if (cps->useSAN) {
4984       char *space;
4985       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4986         int len = space - parseList[moveNum];
4987         memcpy(buf, parseList[moveNum], len);
4988         buf[len++] = '\n';
4989         buf[len] = NULLCHAR;
4990       } else {
4991         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4992       }
4993       SendToProgram(buf, cps);
4994     } else {
4995       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4996         AlphaRank(moveList[moveNum], 4);
4997         SendToProgram(moveList[moveNum], cps);
4998         AlphaRank(moveList[moveNum], 4); // and back
4999       } else
5000       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5001        * the engine. It would be nice to have a better way to identify castle
5002        * moves here. */
5003       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5004                                                                          && cps->useOOCastle) {
5005         int fromX = moveList[moveNum][0] - AAA;
5006         int fromY = moveList[moveNum][1] - ONE;
5007         int toX = moveList[moveNum][2] - AAA;
5008         int toY = moveList[moveNum][3] - ONE;
5009         if((boards[moveNum][fromY][fromX] == WhiteKing
5010             && boards[moveNum][toY][toX] == WhiteRook)
5011            || (boards[moveNum][fromY][fromX] == BlackKing
5012                && boards[moveNum][toY][toX] == BlackRook)) {
5013           if(toX > fromX) SendToProgram("O-O\n", cps);
5014           else SendToProgram("O-O-O\n", cps);
5015         }
5016         else SendToProgram(moveList[moveNum], cps);
5017       } else
5018       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5019         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5020           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5021           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5022                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5023         } else
5024           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5025                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5026         SendToProgram(buf, cps);
5027       }
5028       else SendToProgram(moveList[moveNum], cps);
5029       /* End of additions by Tord */
5030     }
5031
5032     /* [HGM] setting up the opening has brought engine in force mode! */
5033     /*       Send 'go' if we are in a mode where machine should play. */
5034     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5035         (gameMode == TwoMachinesPlay   ||
5036 #if ZIPPY
5037          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5038 #endif
5039          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5040         SendToProgram("go\n", cps);
5041   if (appData.debugMode) {
5042     fprintf(debugFP, "(extra)\n");
5043   }
5044     }
5045     setboardSpoiledMachineBlack = 0;
5046 }
5047
5048 void
5049 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5050 {
5051     char user_move[MSG_SIZ];
5052     char suffix[4];
5053
5054     if(gameInfo.variant == VariantSChess && promoChar) {
5055         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5056         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5057     } else suffix[0] = NULLCHAR;
5058
5059     switch (moveType) {
5060       default:
5061         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5062                 (int)moveType, fromX, fromY, toX, toY);
5063         DisplayError(user_move + strlen("say "), 0);
5064         break;
5065       case WhiteKingSideCastle:
5066       case BlackKingSideCastle:
5067       case WhiteQueenSideCastleWild:
5068       case BlackQueenSideCastleWild:
5069       /* PUSH Fabien */
5070       case WhiteHSideCastleFR:
5071       case BlackHSideCastleFR:
5072       /* POP Fabien */
5073         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5074         break;
5075       case WhiteQueenSideCastle:
5076       case BlackQueenSideCastle:
5077       case WhiteKingSideCastleWild:
5078       case BlackKingSideCastleWild:
5079       /* PUSH Fabien */
5080       case WhiteASideCastleFR:
5081       case BlackASideCastleFR:
5082       /* POP Fabien */
5083         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5084         break;
5085       case WhiteNonPromotion:
5086       case BlackNonPromotion:
5087         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5088         break;
5089       case WhitePromotion:
5090       case BlackPromotion:
5091         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5092           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5093                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5094                 PieceToChar(WhiteFerz));
5095         else if(gameInfo.variant == VariantGreat)
5096           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5097                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5098                 PieceToChar(WhiteMan));
5099         else
5100           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5101                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5102                 promoChar);
5103         break;
5104       case WhiteDrop:
5105       case BlackDrop:
5106       drop:
5107         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5108                  ToUpper(PieceToChar((ChessSquare) fromX)),
5109                  AAA + toX, ONE + toY);
5110         break;
5111       case IllegalMove:  /* could be a variant we don't quite understand */
5112         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5113       case NormalMove:
5114       case WhiteCapturesEnPassant:
5115       case BlackCapturesEnPassant:
5116         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5117                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5118         break;
5119     }
5120     SendToICS(user_move);
5121     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5122         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5123 }
5124
5125 void
5126 UploadGameEvent ()
5127 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5128     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5129     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5130     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5131       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5132       return;
5133     }
5134     if(gameMode != IcsExamining) { // is this ever not the case?
5135         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5136
5137         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5138           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5139         } else { // on FICS we must first go to general examine mode
5140           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5141         }
5142         if(gameInfo.variant != VariantNormal) {
5143             // try figure out wild number, as xboard names are not always valid on ICS
5144             for(i=1; i<=36; i++) {
5145               snprintf(buf, MSG_SIZ, "wild/%d", i);
5146                 if(StringToVariant(buf) == gameInfo.variant) break;
5147             }
5148             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5149             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5150             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5151         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5152         SendToICS(ics_prefix);
5153         SendToICS(buf);
5154         if(startedFromSetupPosition || backwardMostMove != 0) {
5155           fen = PositionToFEN(backwardMostMove, NULL);
5156           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5157             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5158             SendToICS(buf);
5159           } else { // FICS: everything has to set by separate bsetup commands
5160             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5161             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5162             SendToICS(buf);
5163             if(!WhiteOnMove(backwardMostMove)) {
5164                 SendToICS("bsetup tomove black\n");
5165             }
5166             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5167             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5168             SendToICS(buf);
5169             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5170             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5171             SendToICS(buf);
5172             i = boards[backwardMostMove][EP_STATUS];
5173             if(i >= 0) { // set e.p.
5174               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5175                 SendToICS(buf);
5176             }
5177             bsetup++;
5178           }
5179         }
5180       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5181             SendToICS("bsetup done\n"); // switch to normal examining.
5182     }
5183     for(i = backwardMostMove; i<last; i++) {
5184         char buf[20];
5185         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5186         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5187             int len = strlen(moveList[i]);
5188             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5189             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5190         }
5191         SendToICS(buf);
5192     }
5193     SendToICS(ics_prefix);
5194     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5195 }
5196
5197 void
5198 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5199 {
5200     if (rf == DROP_RANK) {
5201       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5202       sprintf(move, "%c@%c%c\n",
5203                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5204     } else {
5205         if (promoChar == 'x' || promoChar == NULLCHAR) {
5206           sprintf(move, "%c%c%c%c\n",
5207                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5208         } else {
5209             sprintf(move, "%c%c%c%c%c\n",
5210                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5211         }
5212     }
5213 }
5214
5215 void
5216 ProcessICSInitScript (FILE *f)
5217 {
5218     char buf[MSG_SIZ];
5219
5220     while (fgets(buf, MSG_SIZ, f)) {
5221         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5222     }
5223
5224     fclose(f);
5225 }
5226
5227
5228 static int lastX, lastY, selectFlag, dragging;
5229
5230 void
5231 Sweep (int step)
5232 {
5233     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5234     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5235     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5236     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5237     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5238     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5239     do {
5240         promoSweep -= step;
5241         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5242         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5243         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5244         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5245         if(!step) step = -1;
5246     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5247             appData.testLegality && (promoSweep == king ||
5248             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5249     if(toX >= 0) {
5250         int victim = boards[currentMove][toY][toX];
5251         boards[currentMove][toY][toX] = promoSweep;
5252         DrawPosition(FALSE, boards[currentMove]);
5253         boards[currentMove][toY][toX] = victim;
5254     } else
5255     ChangeDragPiece(promoSweep);
5256 }
5257
5258 int
5259 PromoScroll (int x, int y)
5260 {
5261   int step = 0;
5262
5263   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5264   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5265   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5266   if(!step) return FALSE;
5267   lastX = x; lastY = y;
5268   if((promoSweep < BlackPawn) == flipView) step = -step;
5269   if(step > 0) selectFlag = 1;
5270   if(!selectFlag) Sweep(step);
5271   return FALSE;
5272 }
5273
5274 void
5275 NextPiece (int step)
5276 {
5277     ChessSquare piece = boards[currentMove][toY][toX];
5278     do {
5279         pieceSweep -= step;
5280         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5281         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5282         if(!step) step = -1;
5283     } while(PieceToChar(pieceSweep) == '.');
5284     boards[currentMove][toY][toX] = pieceSweep;
5285     DrawPosition(FALSE, boards[currentMove]);
5286     boards[currentMove][toY][toX] = piece;
5287 }
5288 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5289 void
5290 AlphaRank (char *move, int n)
5291 {
5292 //    char *p = move, c; int x, y;
5293
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5296     }
5297
5298     if(move[1]=='*' &&
5299        move[2]>='0' && move[2]<='9' &&
5300        move[3]>='a' && move[3]<='x'    ) {
5301         move[1] = '@';
5302         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5303         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5304     } else
5305     if(move[0]>='0' && move[0]<='9' &&
5306        move[1]>='a' && move[1]<='x' &&
5307        move[2]>='0' && move[2]<='9' &&
5308        move[3]>='a' && move[3]<='x'    ) {
5309         /* input move, Shogi -> normal */
5310         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5311         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5312         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5313         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5314     } else
5315     if(move[1]=='@' &&
5316        move[3]>='0' && move[3]<='9' &&
5317        move[2]>='a' && move[2]<='x'    ) {
5318         move[1] = '*';
5319         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5320         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5321     } else
5322     if(
5323        move[0]>='a' && move[0]<='x' &&
5324        move[3]>='0' && move[3]<='9' &&
5325        move[2]>='a' && move[2]<='x'    ) {
5326          /* output move, normal -> Shogi */
5327         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5328         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5329         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5330         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5331         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5332     }
5333     if (appData.debugMode) {
5334         fprintf(debugFP, "   out = '%s'\n", move);
5335     }
5336 }
5337
5338 char yy_textstr[8000];
5339
5340 /* Parser for moves from gnuchess, ICS, or user typein box */
5341 Boolean
5342 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5343 {
5344     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5345
5346     switch (*moveType) {
5347       case WhitePromotion:
5348       case BlackPromotion:
5349       case WhiteNonPromotion:
5350       case BlackNonPromotion:
5351       case NormalMove:
5352       case WhiteCapturesEnPassant:
5353       case BlackCapturesEnPassant:
5354       case WhiteKingSideCastle:
5355       case WhiteQueenSideCastle:
5356       case BlackKingSideCastle:
5357       case BlackQueenSideCastle:
5358       case WhiteKingSideCastleWild:
5359       case WhiteQueenSideCastleWild:
5360       case BlackKingSideCastleWild:
5361       case BlackQueenSideCastleWild:
5362       /* Code added by Tord: */
5363       case WhiteHSideCastleFR:
5364       case WhiteASideCastleFR:
5365       case BlackHSideCastleFR:
5366       case BlackASideCastleFR:
5367       /* End of code added by Tord */
5368       case IllegalMove:         /* bug or odd chess variant */
5369         *fromX = currentMoveString[0] - AAA;
5370         *fromY = currentMoveString[1] - ONE;
5371         *toX = currentMoveString[2] - AAA;
5372         *toY = currentMoveString[3] - ONE;
5373         *promoChar = currentMoveString[4];
5374         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5375             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5376     if (appData.debugMode) {
5377         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5378     }
5379             *fromX = *fromY = *toX = *toY = 0;
5380             return FALSE;
5381         }
5382         if (appData.testLegality) {
5383           return (*moveType != IllegalMove);
5384         } else {
5385           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5386                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5387         }
5388
5389       case WhiteDrop:
5390       case BlackDrop:
5391         *fromX = *moveType == WhiteDrop ?
5392           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5393           (int) CharToPiece(ToLower(currentMoveString[0]));
5394         *fromY = DROP_RANK;
5395         *toX = currentMoveString[2] - AAA;
5396         *toY = currentMoveString[3] - ONE;
5397         *promoChar = NULLCHAR;
5398         return TRUE;
5399
5400       case AmbiguousMove:
5401       case ImpossibleMove:
5402       case EndOfFile:
5403       case ElapsedTime:
5404       case Comment:
5405       case PGNTag:
5406       case NAG:
5407       case WhiteWins:
5408       case BlackWins:
5409       case GameIsDrawn:
5410       default:
5411     if (appData.debugMode) {
5412         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5413     }
5414         /* bug? */
5415         *fromX = *fromY = *toX = *toY = 0;
5416         *promoChar = NULLCHAR;
5417         return FALSE;
5418     }
5419 }
5420
5421 Boolean pushed = FALSE;
5422 char *lastParseAttempt;
5423
5424 void
5425 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5426 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5427   int fromX, fromY, toX, toY; char promoChar;
5428   ChessMove moveType;
5429   Boolean valid;
5430   int nr = 0;
5431
5432   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5433   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5434     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5435     pushed = TRUE;
5436   }
5437   endPV = forwardMostMove;
5438   do {
5439     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5440     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5441     lastParseAttempt = pv;
5442     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5443     if(!valid && nr == 0 &&
5444        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5445         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5446         // Hande case where played move is different from leading PV move
5447         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5448         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5449         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5450         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5451           endPV += 2; // if position different, keep this
5452           moveList[endPV-1][0] = fromX + AAA;
5453           moveList[endPV-1][1] = fromY + ONE;
5454           moveList[endPV-1][2] = toX + AAA;
5455           moveList[endPV-1][3] = toY + ONE;
5456           parseList[endPV-1][0] = NULLCHAR;
5457           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5458         }
5459       }
5460     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5461     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5462     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5463     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5464         valid++; // allow comments in PV
5465         continue;
5466     }
5467     nr++;
5468     if(endPV+1 > framePtr) break; // no space, truncate
5469     if(!valid) break;
5470     endPV++;
5471     CopyBoard(boards[endPV], boards[endPV-1]);
5472     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5473     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5474     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5475     CoordsToAlgebraic(boards[endPV - 1],
5476                              PosFlags(endPV - 1),
5477                              fromY, fromX, toY, toX, promoChar,
5478                              parseList[endPV - 1]);
5479   } while(valid);
5480   if(atEnd == 2) return; // used hidden, for PV conversion
5481   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5482   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5483   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5484                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5485   DrawPosition(TRUE, boards[currentMove]);
5486 }
5487
5488 int
5489 MultiPV (ChessProgramState *cps)
5490 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5491         int i;
5492         for(i=0; i<cps->nrOptions; i++)
5493             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5494                 return i;
5495         return -1;
5496 }
5497
5498 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5499
5500 Boolean
5501 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5502 {
5503         int startPV, multi, lineStart, origIndex = index;
5504         char *p, buf2[MSG_SIZ];
5505         ChessProgramState *cps = (pane ? &second : &first);
5506
5507         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5508         lastX = x; lastY = y;
5509         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5510         lineStart = startPV = index;
5511         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5512         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5513         index = startPV;
5514         do{ while(buf[index] && buf[index] != '\n') index++;
5515         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5516         buf[index] = 0;
5517         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5518                 int n = cps->option[multi].value;
5519                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5520                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5521                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5522                 cps->option[multi].value = n;
5523                 *start = *end = 0;
5524                 return FALSE;
5525         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5526                 ExcludeClick(origIndex - lineStart);
5527                 return FALSE;
5528         }
5529         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5530         *start = startPV; *end = index-1;
5531         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5532         return TRUE;
5533 }
5534
5535 char *
5536 PvToSAN (char *pv)
5537 {
5538         static char buf[10*MSG_SIZ];
5539         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5540         *buf = NULLCHAR;
5541         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5542         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5543         for(i = forwardMostMove; i<endPV; i++){
5544             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5545             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5546             k += strlen(buf+k);
5547         }
5548         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5549         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5550         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5551         endPV = savedEnd;
5552         return buf;
5553 }
5554
5555 Boolean
5556 LoadPV (int x, int y)
5557 { // called on right mouse click to load PV
5558   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5559   lastX = x; lastY = y;
5560   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5561   extendGame = FALSE;
5562   return TRUE;
5563 }
5564
5565 void
5566 UnLoadPV ()
5567 {
5568   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5569   if(endPV < 0) return;
5570   if(appData.autoCopyPV) CopyFENToClipboard();
5571   endPV = -1;
5572   if(extendGame && currentMove > forwardMostMove) {
5573         Boolean saveAnimate = appData.animate;
5574         if(pushed) {
5575             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5576                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5577             } else storedGames--; // abandon shelved tail of original game
5578         }
5579         pushed = FALSE;
5580         forwardMostMove = currentMove;
5581         currentMove = oldFMM;
5582         appData.animate = FALSE;
5583         ToNrEvent(forwardMostMove);
5584         appData.animate = saveAnimate;
5585   }
5586   currentMove = forwardMostMove;
5587   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5588   ClearPremoveHighlights();
5589   DrawPosition(TRUE, boards[currentMove]);
5590 }
5591
5592 void
5593 MovePV (int x, int y, int h)
5594 { // step through PV based on mouse coordinates (called on mouse move)
5595   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5596
5597   // we must somehow check if right button is still down (might be released off board!)
5598   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5599   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5600   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5601   if(!step) return;
5602   lastX = x; lastY = y;
5603
5604   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5605   if(endPV < 0) return;
5606   if(y < margin) step = 1; else
5607   if(y > h - margin) step = -1;
5608   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5609   currentMove += step;
5610   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5611   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5612                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5613   DrawPosition(FALSE, boards[currentMove]);
5614 }
5615
5616
5617 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5618 // All positions will have equal probability, but the current method will not provide a unique
5619 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5620 #define DARK 1
5621 #define LITE 2
5622 #define ANY 3
5623
5624 int squaresLeft[4];
5625 int piecesLeft[(int)BlackPawn];
5626 int seed, nrOfShuffles;
5627
5628 void
5629 GetPositionNumber ()
5630 {       // sets global variable seed
5631         int i;
5632
5633         seed = appData.defaultFrcPosition;
5634         if(seed < 0) { // randomize based on time for negative FRC position numbers
5635                 for(i=0; i<50; i++) seed += random();
5636                 seed = random() ^ random() >> 8 ^ random() << 8;
5637                 if(seed<0) seed = -seed;
5638         }
5639 }
5640
5641 int
5642 put (Board board, int pieceType, int rank, int n, int shade)
5643 // put the piece on the (n-1)-th empty squares of the given shade
5644 {
5645         int i;
5646
5647         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5648                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5649                         board[rank][i] = (ChessSquare) pieceType;
5650                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5651                         squaresLeft[ANY]--;
5652                         piecesLeft[pieceType]--;
5653                         return i;
5654                 }
5655         }
5656         return -1;
5657 }
5658
5659
5660 void
5661 AddOnePiece (Board board, int pieceType, int rank, int shade)
5662 // calculate where the next piece goes, (any empty square), and put it there
5663 {
5664         int i;
5665
5666         i = seed % squaresLeft[shade];
5667         nrOfShuffles *= squaresLeft[shade];
5668         seed /= squaresLeft[shade];
5669         put(board, pieceType, rank, i, shade);
5670 }
5671
5672 void
5673 AddTwoPieces (Board board, int pieceType, int rank)
5674 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5675 {
5676         int i, n=squaresLeft[ANY], j=n-1, k;
5677
5678         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5679         i = seed % k;  // pick one
5680         nrOfShuffles *= k;
5681         seed /= k;
5682         while(i >= j) i -= j--;
5683         j = n - 1 - j; i += j;
5684         put(board, pieceType, rank, j, ANY);
5685         put(board, pieceType, rank, i, ANY);
5686 }
5687
5688 void
5689 SetUpShuffle (Board board, int number)
5690 {
5691         int i, p, first=1;
5692
5693         GetPositionNumber(); nrOfShuffles = 1;
5694
5695         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5696         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5697         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5698
5699         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5700
5701         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5702             p = (int) board[0][i];
5703             if(p < (int) BlackPawn) piecesLeft[p] ++;
5704             board[0][i] = EmptySquare;
5705         }
5706
5707         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5708             // shuffles restricted to allow normal castling put KRR first
5709             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5710                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5711             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5712                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5713             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5714                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5715             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5716                 put(board, WhiteRook, 0, 0, ANY);
5717             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5718         }
5719
5720         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5721             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5722             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5723                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5724                 while(piecesLeft[p] >= 2) {
5725                     AddOnePiece(board, p, 0, LITE);
5726                     AddOnePiece(board, p, 0, DARK);
5727                 }
5728                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5729             }
5730
5731         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5732             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5733             // but we leave King and Rooks for last, to possibly obey FRC restriction
5734             if(p == (int)WhiteRook) continue;
5735             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5736             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5737         }
5738
5739         // now everything is placed, except perhaps King (Unicorn) and Rooks
5740
5741         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5742             // Last King gets castling rights
5743             while(piecesLeft[(int)WhiteUnicorn]) {
5744                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5745                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5746             }
5747
5748             while(piecesLeft[(int)WhiteKing]) {
5749                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5750                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5751             }
5752
5753
5754         } else {
5755             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5756             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5757         }
5758
5759         // Only Rooks can be left; simply place them all
5760         while(piecesLeft[(int)WhiteRook]) {
5761                 i = put(board, WhiteRook, 0, 0, ANY);
5762                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5763                         if(first) {
5764                                 first=0;
5765                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5766                         }
5767                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5768                 }
5769         }
5770         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5771             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5772         }
5773
5774         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5775 }
5776
5777 int
5778 SetCharTable (char *table, const char * map)
5779 /* [HGM] moved here from winboard.c because of its general usefulness */
5780 /*       Basically a safe strcpy that uses the last character as King */
5781 {
5782     int result = FALSE; int NrPieces;
5783
5784     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5785                     && NrPieces >= 12 && !(NrPieces&1)) {
5786         int i; /* [HGM] Accept even length from 12 to 34 */
5787
5788         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5789         for( i=0; i<NrPieces/2-1; i++ ) {
5790             table[i] = map[i];
5791             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5792         }
5793         table[(int) WhiteKing]  = map[NrPieces/2-1];
5794         table[(int) BlackKing]  = map[NrPieces-1];
5795
5796         result = TRUE;
5797     }
5798
5799     return result;
5800 }
5801
5802 void
5803 Prelude (Board board)
5804 {       // [HGM] superchess: random selection of exo-pieces
5805         int i, j, k; ChessSquare p;
5806         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5807
5808         GetPositionNumber(); // use FRC position number
5809
5810         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5811             SetCharTable(pieceToChar, appData.pieceToCharTable);
5812             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5813                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5814         }
5815
5816         j = seed%4;                 seed /= 4;
5817         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5818         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5819         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5820         j = seed%3 + (seed%3 >= j); seed /= 3;
5821         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5822         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5823         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5824         j = seed%3;                 seed /= 3;
5825         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5826         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5827         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5828         j = seed%2 + (seed%2 >= j); seed /= 2;
5829         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5830         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5831         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5832         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5833         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5834         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5835         put(board, exoPieces[0],    0, 0, ANY);
5836         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5837 }
5838
5839 void
5840 InitPosition (int redraw)
5841 {
5842     ChessSquare (* pieces)[BOARD_FILES];
5843     int i, j, pawnRow, overrule,
5844     oldx = gameInfo.boardWidth,
5845     oldy = gameInfo.boardHeight,
5846     oldh = gameInfo.holdingsWidth;
5847     static int oldv;
5848
5849     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5850
5851     /* [AS] Initialize pv info list [HGM] and game status */
5852     {
5853         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5854             pvInfoList[i].depth = 0;
5855             boards[i][EP_STATUS] = EP_NONE;
5856             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5857         }
5858
5859         initialRulePlies = 0; /* 50-move counter start */
5860
5861         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5862         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5863     }
5864
5865
5866     /* [HGM] logic here is completely changed. In stead of full positions */
5867     /* the initialized data only consist of the two backranks. The switch */
5868     /* selects which one we will use, which is than copied to the Board   */
5869     /* initialPosition, which for the rest is initialized by Pawns and    */
5870     /* empty squares. This initial position is then copied to boards[0],  */
5871     /* possibly after shuffling, so that it remains available.            */
5872
5873     gameInfo.holdingsWidth = 0; /* default board sizes */
5874     gameInfo.boardWidth    = 8;
5875     gameInfo.boardHeight   = 8;
5876     gameInfo.holdingsSize  = 0;
5877     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5878     for(i=0; i<BOARD_FILES-2; i++)
5879       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5880     initialPosition[EP_STATUS] = EP_NONE;
5881     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5882     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5883          SetCharTable(pieceNickName, appData.pieceNickNames);
5884     else SetCharTable(pieceNickName, "............");
5885     pieces = FIDEArray;
5886
5887     switch (gameInfo.variant) {
5888     case VariantFischeRandom:
5889       shuffleOpenings = TRUE;
5890     default:
5891       break;
5892     case VariantShatranj:
5893       pieces = ShatranjArray;
5894       nrCastlingRights = 0;
5895       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5896       break;
5897     case VariantMakruk:
5898       pieces = makrukArray;
5899       nrCastlingRights = 0;
5900       startedFromSetupPosition = TRUE;
5901       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5902       break;
5903     case VariantTwoKings:
5904       pieces = twoKingsArray;
5905       break;
5906     case VariantGrand:
5907       pieces = GrandArray;
5908       nrCastlingRights = 0;
5909       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5910       gameInfo.boardWidth = 10;
5911       gameInfo.boardHeight = 10;
5912       gameInfo.holdingsSize = 7;
5913       break;
5914     case VariantCapaRandom:
5915       shuffleOpenings = TRUE;
5916     case VariantCapablanca:
5917       pieces = CapablancaArray;
5918       gameInfo.boardWidth = 10;
5919       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5920       break;
5921     case VariantGothic:
5922       pieces = GothicArray;
5923       gameInfo.boardWidth = 10;
5924       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5925       break;
5926     case VariantSChess:
5927       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5928       gameInfo.holdingsSize = 7;
5929       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5930       break;
5931     case VariantJanus:
5932       pieces = JanusArray;
5933       gameInfo.boardWidth = 10;
5934       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5935       nrCastlingRights = 6;
5936         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5937         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5938         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5939         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5940         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5941         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5942       break;
5943     case VariantFalcon:
5944       pieces = FalconArray;
5945       gameInfo.boardWidth = 10;
5946       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5947       break;
5948     case VariantXiangqi:
5949       pieces = XiangqiArray;
5950       gameInfo.boardWidth  = 9;
5951       gameInfo.boardHeight = 10;
5952       nrCastlingRights = 0;
5953       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5954       break;
5955     case VariantShogi:
5956       pieces = ShogiArray;
5957       gameInfo.boardWidth  = 9;
5958       gameInfo.boardHeight = 9;
5959       gameInfo.holdingsSize = 7;
5960       nrCastlingRights = 0;
5961       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5962       break;
5963     case VariantCourier:
5964       pieces = CourierArray;
5965       gameInfo.boardWidth  = 12;
5966       nrCastlingRights = 0;
5967       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5968       break;
5969     case VariantKnightmate:
5970       pieces = KnightmateArray;
5971       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5972       break;
5973     case VariantSpartan:
5974       pieces = SpartanArray;
5975       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5976       break;
5977     case VariantFairy:
5978       pieces = fairyArray;
5979       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5980       break;
5981     case VariantGreat:
5982       pieces = GreatArray;
5983       gameInfo.boardWidth = 10;
5984       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5985       gameInfo.holdingsSize = 8;
5986       break;
5987     case VariantSuper:
5988       pieces = FIDEArray;
5989       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5990       gameInfo.holdingsSize = 8;
5991       startedFromSetupPosition = TRUE;
5992       break;
5993     case VariantCrazyhouse:
5994     case VariantBughouse:
5995       pieces = FIDEArray;
5996       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5997       gameInfo.holdingsSize = 5;
5998       break;
5999     case VariantWildCastle:
6000       pieces = FIDEArray;
6001       /* !!?shuffle with kings guaranteed to be on d or e file */
6002       shuffleOpenings = 1;
6003       break;
6004     case VariantNoCastle:
6005       pieces = FIDEArray;
6006       nrCastlingRights = 0;
6007       /* !!?unconstrained back-rank shuffle */
6008       shuffleOpenings = 1;
6009       break;
6010     }
6011
6012     overrule = 0;
6013     if(appData.NrFiles >= 0) {
6014         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6015         gameInfo.boardWidth = appData.NrFiles;
6016     }
6017     if(appData.NrRanks >= 0) {
6018         gameInfo.boardHeight = appData.NrRanks;
6019     }
6020     if(appData.holdingsSize >= 0) {
6021         i = appData.holdingsSize;
6022         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6023         gameInfo.holdingsSize = i;
6024     }
6025     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6026     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6027         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6028
6029     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6030     if(pawnRow < 1) pawnRow = 1;
6031     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6032
6033     /* User pieceToChar list overrules defaults */
6034     if(appData.pieceToCharTable != NULL)
6035         SetCharTable(pieceToChar, appData.pieceToCharTable);
6036
6037     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6038
6039         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6040             s = (ChessSquare) 0; /* account holding counts in guard band */
6041         for( i=0; i<BOARD_HEIGHT; i++ )
6042             initialPosition[i][j] = s;
6043
6044         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6045         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6046         initialPosition[pawnRow][j] = WhitePawn;
6047         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6048         if(gameInfo.variant == VariantXiangqi) {
6049             if(j&1) {
6050                 initialPosition[pawnRow][j] =
6051                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6052                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6053                    initialPosition[2][j] = WhiteCannon;
6054                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6055                 }
6056             }
6057         }
6058         if(gameInfo.variant == VariantGrand) {
6059             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6060                initialPosition[0][j] = WhiteRook;
6061                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6062             }
6063         }
6064         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6065     }
6066     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6067
6068             j=BOARD_LEFT+1;
6069             initialPosition[1][j] = WhiteBishop;
6070             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6071             j=BOARD_RGHT-2;
6072             initialPosition[1][j] = WhiteRook;
6073             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6074     }
6075
6076     if( nrCastlingRights == -1) {
6077         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6078         /*       This sets default castling rights from none to normal corners   */
6079         /* Variants with other castling rights must set them themselves above    */
6080         nrCastlingRights = 6;
6081
6082         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6083         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6084         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6085         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6086         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6087         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6088      }
6089
6090      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6091      if(gameInfo.variant == VariantGreat) { // promotion commoners
6092         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6093         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6094         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6095         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6096      }
6097      if( gameInfo.variant == VariantSChess ) {
6098       initialPosition[1][0] = BlackMarshall;
6099       initialPosition[2][0] = BlackAngel;
6100       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6101       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6102       initialPosition[1][1] = initialPosition[2][1] =
6103       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6104      }
6105   if (appData.debugMode) {
6106     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6107   }
6108     if(shuffleOpenings) {
6109         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6110         startedFromSetupPosition = TRUE;
6111     }
6112     if(startedFromPositionFile) {
6113       /* [HGM] loadPos: use PositionFile for every new game */
6114       CopyBoard(initialPosition, filePosition);
6115       for(i=0; i<nrCastlingRights; i++)
6116           initialRights[i] = filePosition[CASTLING][i];
6117       startedFromSetupPosition = TRUE;
6118     }
6119
6120     CopyBoard(boards[0], initialPosition);
6121
6122     if(oldx != gameInfo.boardWidth ||
6123        oldy != gameInfo.boardHeight ||
6124        oldv != gameInfo.variant ||
6125        oldh != gameInfo.holdingsWidth
6126                                          )
6127             InitDrawingSizes(-2 ,0);
6128
6129     oldv = gameInfo.variant;
6130     if (redraw)
6131       DrawPosition(TRUE, boards[currentMove]);
6132 }
6133
6134 void
6135 SendBoard (ChessProgramState *cps, int moveNum)
6136 {
6137     char message[MSG_SIZ];
6138
6139     if (cps->useSetboard) {
6140       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6141       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6142       SendToProgram(message, cps);
6143       free(fen);
6144
6145     } else {
6146       ChessSquare *bp;
6147       int i, j, left=0, right=BOARD_WIDTH;
6148       /* Kludge to set black to move, avoiding the troublesome and now
6149        * deprecated "black" command.
6150        */
6151       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6152         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6153
6154       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6155
6156       SendToProgram("edit\n", cps);
6157       SendToProgram("#\n", cps);
6158       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6159         bp = &boards[moveNum][i][left];
6160         for (j = left; j < right; j++, bp++) {
6161           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6162           if ((int) *bp < (int) BlackPawn) {
6163             if(j == BOARD_RGHT+1)
6164                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6165             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6166             if(message[0] == '+' || message[0] == '~') {
6167               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6168                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6169                         AAA + j, ONE + i);
6170             }
6171             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6172                 message[1] = BOARD_RGHT   - 1 - j + '1';
6173                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6174             }
6175             SendToProgram(message, cps);
6176           }
6177         }
6178       }
6179
6180       SendToProgram("c\n", cps);
6181       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6182         bp = &boards[moveNum][i][left];
6183         for (j = left; j < right; j++, bp++) {
6184           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6185           if (((int) *bp != (int) EmptySquare)
6186               && ((int) *bp >= (int) BlackPawn)) {
6187             if(j == BOARD_LEFT-2)
6188                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6189             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6190                     AAA + j, ONE + i);
6191             if(message[0] == '+' || message[0] == '~') {
6192               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6193                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6194                         AAA + j, ONE + i);
6195             }
6196             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6197                 message[1] = BOARD_RGHT   - 1 - j + '1';
6198                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6199             }
6200             SendToProgram(message, cps);
6201           }
6202         }
6203       }
6204
6205       SendToProgram(".\n", cps);
6206     }
6207     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6208 }
6209
6210 char exclusionHeader[MSG_SIZ];
6211 int exCnt, excludePtr;
6212 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6213 static Exclusion excluTab[200];
6214 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6215
6216 static void
6217 WriteMap (int s)
6218 {
6219     int j;
6220     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6221     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6222 }
6223
6224 static void
6225 ClearMap ()
6226 {
6227     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6228     excludePtr = 24; exCnt = 0;
6229     WriteMap(0);
6230 }
6231
6232 static void
6233 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6234 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6235     char buf[2*MOVE_LEN], *p;
6236     Exclusion *e = excluTab;
6237     int i;
6238     for(i=0; i<exCnt; i++)
6239         if(e[i].ff == fromX && e[i].fr == fromY &&
6240            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6241     if(i == exCnt) { // was not in exclude list; add it
6242         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6243         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6244             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6245             return; // abort
6246         }
6247         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6248         excludePtr++; e[i].mark = excludePtr++;
6249         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6250         exCnt++;
6251     }
6252     exclusionHeader[e[i].mark] = state;
6253 }
6254
6255 static int
6256 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6257 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6258     char buf[MSG_SIZ];
6259     int j, k;
6260     ChessMove moveType;
6261     if((signed char)promoChar == -1) { // kludge to indicate best move
6262         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6263             return 1; // if unparsable, abort
6264     }
6265     // update exclusion map (resolving toggle by consulting existing state)
6266     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6267     j = k%8; k >>= 3;
6268     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6269     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6270          excludeMap[k] |=   1<<j;
6271     else excludeMap[k] &= ~(1<<j);
6272     // update header
6273     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6274     // inform engine
6275     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6276     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6277     SendToBoth(buf);
6278     return (state == '+');
6279 }
6280
6281 static void
6282 ExcludeClick (int index)
6283 {
6284     int i, j;
6285     Exclusion *e = excluTab;
6286     if(index < 25) { // none, best or tail clicked
6287         if(index < 13) { // none: include all
6288             WriteMap(0); // clear map
6289             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6290             SendToBoth("include all\n"); // and inform engine
6291         } else if(index > 18) { // tail
6292             if(exclusionHeader[19] == '-') { // tail was excluded
6293                 SendToBoth("include all\n");
6294                 WriteMap(0); // clear map completely
6295                 // now re-exclude selected moves
6296                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6297                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6298             } else { // tail was included or in mixed state
6299                 SendToBoth("exclude all\n");
6300                 WriteMap(0xFF); // fill map completely
6301                 // now re-include selected moves
6302                 j = 0; // count them
6303                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6304                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6305                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6306             }
6307         } else { // best
6308             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6309         }
6310     } else {
6311         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6312             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6313             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6314             break;
6315         }
6316     }
6317 }
6318
6319 ChessSquare
6320 DefaultPromoChoice (int white)
6321 {
6322     ChessSquare result;
6323     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6324         result = WhiteFerz; // no choice
6325     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6326         result= WhiteKing; // in Suicide Q is the last thing we want
6327     else if(gameInfo.variant == VariantSpartan)
6328         result = white ? WhiteQueen : WhiteAngel;
6329     else result = WhiteQueen;
6330     if(!white) result = WHITE_TO_BLACK result;
6331     return result;
6332 }
6333
6334 static int autoQueen; // [HGM] oneclick
6335
6336 int
6337 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6338 {
6339     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6340     /* [HGM] add Shogi promotions */
6341     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6342     ChessSquare piece;
6343     ChessMove moveType;
6344     Boolean premove;
6345
6346     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6347     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6348
6349     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6350       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6351         return FALSE;
6352
6353     piece = boards[currentMove][fromY][fromX];
6354     if(gameInfo.variant == VariantShogi) {
6355         promotionZoneSize = BOARD_HEIGHT/3;
6356         highestPromotingPiece = (int)WhiteFerz;
6357     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6358         promotionZoneSize = 3;
6359     }
6360
6361     // Treat Lance as Pawn when it is not representing Amazon
6362     if(gameInfo.variant != VariantSuper) {
6363         if(piece == WhiteLance) piece = WhitePawn; else
6364         if(piece == BlackLance) piece = BlackPawn;
6365     }
6366
6367     // next weed out all moves that do not touch the promotion zone at all
6368     if((int)piece >= BlackPawn) {
6369         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6370              return FALSE;
6371         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6372     } else {
6373         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6374            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6375     }
6376
6377     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6378
6379     // weed out mandatory Shogi promotions
6380     if(gameInfo.variant == VariantShogi) {
6381         if(piece >= BlackPawn) {
6382             if(toY == 0 && piece == BlackPawn ||
6383                toY == 0 && piece == BlackQueen ||
6384                toY <= 1 && piece == BlackKnight) {
6385                 *promoChoice = '+';
6386                 return FALSE;
6387             }
6388         } else {
6389             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6390                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6391                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6392                 *promoChoice = '+';
6393                 return FALSE;
6394             }
6395         }
6396     }
6397
6398     // weed out obviously illegal Pawn moves
6399     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6400         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6401         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6402         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6403         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6404         // note we are not allowed to test for valid (non-)capture, due to premove
6405     }
6406
6407     // we either have a choice what to promote to, or (in Shogi) whether to promote
6408     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6409         *promoChoice = PieceToChar(BlackFerz);  // no choice
6410         return FALSE;
6411     }
6412     // no sense asking what we must promote to if it is going to explode...
6413     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6414         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6415         return FALSE;
6416     }
6417     // give caller the default choice even if we will not make it
6418     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6419     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6420     if(        sweepSelect && gameInfo.variant != VariantGreat
6421                            && gameInfo.variant != VariantGrand
6422                            && gameInfo.variant != VariantSuper) return FALSE;
6423     if(autoQueen) return FALSE; // predetermined
6424
6425     // suppress promotion popup on illegal moves that are not premoves
6426     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6427               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6428     if(appData.testLegality && !premove) {
6429         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6430                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6431         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6432             return FALSE;
6433     }
6434
6435     return TRUE;
6436 }
6437
6438 int
6439 InPalace (int row, int column)
6440 {   /* [HGM] for Xiangqi */
6441     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6442          column < (BOARD_WIDTH + 4)/2 &&
6443          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6444     return FALSE;
6445 }
6446
6447 int
6448 PieceForSquare (int x, int y)
6449 {
6450   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6451      return -1;
6452   else
6453      return boards[currentMove][y][x];
6454 }
6455
6456 int
6457 OKToStartUserMove (int x, int y)
6458 {
6459     ChessSquare from_piece;
6460     int white_piece;
6461
6462     if (matchMode) return FALSE;
6463     if (gameMode == EditPosition) return TRUE;
6464
6465     if (x >= 0 && y >= 0)
6466       from_piece = boards[currentMove][y][x];
6467     else
6468       from_piece = EmptySquare;
6469
6470     if (from_piece == EmptySquare) return FALSE;
6471
6472     white_piece = (int)from_piece >= (int)WhitePawn &&
6473       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6474
6475     switch (gameMode) {
6476       case AnalyzeFile:
6477       case TwoMachinesPlay:
6478       case EndOfGame:
6479         return FALSE;
6480
6481       case IcsObserving:
6482       case IcsIdle:
6483         return FALSE;
6484
6485       case MachinePlaysWhite:
6486       case IcsPlayingBlack:
6487         if (appData.zippyPlay) return FALSE;
6488         if (white_piece) {
6489             DisplayMoveError(_("You are playing Black"));
6490             return FALSE;
6491         }
6492         break;
6493
6494       case MachinePlaysBlack:
6495       case IcsPlayingWhite:
6496         if (appData.zippyPlay) return FALSE;
6497         if (!white_piece) {
6498             DisplayMoveError(_("You are playing White"));
6499             return FALSE;
6500         }
6501         break;
6502
6503       case PlayFromGameFile:
6504             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6505       case EditGame:
6506         if (!white_piece && WhiteOnMove(currentMove)) {
6507             DisplayMoveError(_("It is White's turn"));
6508             return FALSE;
6509         }
6510         if (white_piece && !WhiteOnMove(currentMove)) {
6511             DisplayMoveError(_("It is Black's turn"));
6512             return FALSE;
6513         }
6514         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6515             /* Editing correspondence game history */
6516             /* Could disallow this or prompt for confirmation */
6517             cmailOldMove = -1;
6518         }
6519         break;
6520
6521       case BeginningOfGame:
6522         if (appData.icsActive) return FALSE;
6523         if (!appData.noChessProgram) {
6524             if (!white_piece) {
6525                 DisplayMoveError(_("You are playing White"));
6526                 return FALSE;
6527             }
6528         }
6529         break;
6530
6531       case Training:
6532         if (!white_piece && WhiteOnMove(currentMove)) {
6533             DisplayMoveError(_("It is White's turn"));
6534             return FALSE;
6535         }
6536         if (white_piece && !WhiteOnMove(currentMove)) {
6537             DisplayMoveError(_("It is Black's turn"));
6538             return FALSE;
6539         }
6540         break;
6541
6542       default:
6543       case IcsExamining:
6544         break;
6545     }
6546     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6547         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6548         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6549         && gameMode != AnalyzeFile && gameMode != Training) {
6550         DisplayMoveError(_("Displayed position is not current"));
6551         return FALSE;
6552     }
6553     return TRUE;
6554 }
6555
6556 Boolean
6557 OnlyMove (int *x, int *y, Boolean captures)
6558 {
6559     DisambiguateClosure cl;
6560     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6561     switch(gameMode) {
6562       case MachinePlaysBlack:
6563       case IcsPlayingWhite:
6564       case BeginningOfGame:
6565         if(!WhiteOnMove(currentMove)) return FALSE;
6566         break;
6567       case MachinePlaysWhite:
6568       case IcsPlayingBlack:
6569         if(WhiteOnMove(currentMove)) return FALSE;
6570         break;
6571       case EditGame:
6572         break;
6573       default:
6574         return FALSE;
6575     }
6576     cl.pieceIn = EmptySquare;
6577     cl.rfIn = *y;
6578     cl.ffIn = *x;
6579     cl.rtIn = -1;
6580     cl.ftIn = -1;
6581     cl.promoCharIn = NULLCHAR;
6582     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6583     if( cl.kind == NormalMove ||
6584         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6585         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6586         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6587       fromX = cl.ff;
6588       fromY = cl.rf;
6589       *x = cl.ft;
6590       *y = cl.rt;
6591       return TRUE;
6592     }
6593     if(cl.kind != ImpossibleMove) return FALSE;
6594     cl.pieceIn = EmptySquare;
6595     cl.rfIn = -1;
6596     cl.ffIn = -1;
6597     cl.rtIn = *y;
6598     cl.ftIn = *x;
6599     cl.promoCharIn = NULLCHAR;
6600     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6601     if( cl.kind == NormalMove ||
6602         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6603         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6604         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6605       fromX = cl.ff;
6606       fromY = cl.rf;
6607       *x = cl.ft;
6608       *y = cl.rt;
6609       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6610       return TRUE;
6611     }
6612     return FALSE;
6613 }
6614
6615 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6616 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6617 int lastLoadGameUseList = FALSE;
6618 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6619 ChessMove lastLoadGameStart = EndOfFile;
6620 int doubleClick;
6621
6622 void
6623 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6624 {
6625     ChessMove moveType;
6626     ChessSquare pup;
6627     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6628
6629     /* Check if the user is playing in turn.  This is complicated because we
6630        let the user "pick up" a piece before it is his turn.  So the piece he
6631        tried to pick up may have been captured by the time he puts it down!
6632        Therefore we use the color the user is supposed to be playing in this
6633        test, not the color of the piece that is currently on the starting
6634        square---except in EditGame mode, where the user is playing both
6635        sides; fortunately there the capture race can't happen.  (It can
6636        now happen in IcsExamining mode, but that's just too bad.  The user
6637        will get a somewhat confusing message in that case.)
6638        */
6639
6640     switch (gameMode) {
6641       case AnalyzeFile:
6642       case TwoMachinesPlay:
6643       case EndOfGame:
6644       case IcsObserving:
6645       case IcsIdle:
6646         /* We switched into a game mode where moves are not accepted,
6647            perhaps while the mouse button was down. */
6648         return;
6649
6650       case MachinePlaysWhite:
6651         /* User is moving for Black */
6652         if (WhiteOnMove(currentMove)) {
6653             DisplayMoveError(_("It is White's turn"));
6654             return;
6655         }
6656         break;
6657
6658       case MachinePlaysBlack:
6659         /* User is moving for White */
6660         if (!WhiteOnMove(currentMove)) {
6661             DisplayMoveError(_("It is Black's turn"));
6662             return;
6663         }
6664         break;
6665
6666       case PlayFromGameFile:
6667             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6668       case EditGame:
6669       case IcsExamining:
6670       case BeginningOfGame:
6671       case AnalyzeMode:
6672       case Training:
6673         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6674         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6675             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6676             /* User is moving for Black */
6677             if (WhiteOnMove(currentMove)) {
6678                 DisplayMoveError(_("It is White's turn"));
6679                 return;
6680             }
6681         } else {
6682             /* User is moving for White */
6683             if (!WhiteOnMove(currentMove)) {
6684                 DisplayMoveError(_("It is Black's turn"));
6685                 return;
6686             }
6687         }
6688         break;
6689
6690       case IcsPlayingBlack:
6691         /* User is moving for Black */
6692         if (WhiteOnMove(currentMove)) {
6693             if (!appData.premove) {
6694                 DisplayMoveError(_("It is White's turn"));
6695             } else if (toX >= 0 && toY >= 0) {
6696                 premoveToX = toX;
6697                 premoveToY = toY;
6698                 premoveFromX = fromX;
6699                 premoveFromY = fromY;
6700                 premovePromoChar = promoChar;
6701                 gotPremove = 1;
6702                 if (appData.debugMode)
6703                     fprintf(debugFP, "Got premove: fromX %d,"
6704                             "fromY %d, toX %d, toY %d\n",
6705                             fromX, fromY, toX, toY);
6706             }
6707             return;
6708         }
6709         break;
6710
6711       case IcsPlayingWhite:
6712         /* User is moving for White */
6713         if (!WhiteOnMove(currentMove)) {
6714             if (!appData.premove) {
6715                 DisplayMoveError(_("It is Black's turn"));
6716             } else if (toX >= 0 && toY >= 0) {
6717                 premoveToX = toX;
6718                 premoveToY = toY;
6719                 premoveFromX = fromX;
6720                 premoveFromY = fromY;
6721                 premovePromoChar = promoChar;
6722                 gotPremove = 1;
6723                 if (appData.debugMode)
6724                     fprintf(debugFP, "Got premove: fromX %d,"
6725                             "fromY %d, toX %d, toY %d\n",
6726                             fromX, fromY, toX, toY);
6727             }
6728             return;
6729         }
6730         break;
6731
6732       default:
6733         break;
6734
6735       case EditPosition:
6736         /* EditPosition, empty square, or different color piece;
6737            click-click move is possible */
6738         if (toX == -2 || toY == -2) {
6739             boards[0][fromY][fromX] = EmptySquare;
6740             DrawPosition(FALSE, boards[currentMove]);
6741             return;
6742         } else if (toX >= 0 && toY >= 0) {
6743             boards[0][toY][toX] = boards[0][fromY][fromX];
6744             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6745                 if(boards[0][fromY][0] != EmptySquare) {
6746                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6747                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6748                 }
6749             } else
6750             if(fromX == BOARD_RGHT+1) {
6751                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6752                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6753                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6754                 }
6755             } else
6756             boards[0][fromY][fromX] = gatingPiece;
6757             DrawPosition(FALSE, boards[currentMove]);
6758             return;
6759         }
6760         return;
6761     }
6762
6763     if(toX < 0 || toY < 0) return;
6764     pup = boards[currentMove][toY][toX];
6765
6766     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6767     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6768          if( pup != EmptySquare ) return;
6769          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6770            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6771                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6772            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6773            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6774            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6775            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6776          fromY = DROP_RANK;
6777     }
6778
6779     /* [HGM] always test for legality, to get promotion info */
6780     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6781                                          fromY, fromX, toY, toX, promoChar);
6782
6783     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6784
6785     /* [HGM] but possibly ignore an IllegalMove result */
6786     if (appData.testLegality) {
6787         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6788             DisplayMoveError(_("Illegal move"));
6789             return;
6790         }
6791     }
6792
6793     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6794         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6795              ClearPremoveHighlights(); // was included
6796         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6797         return;
6798     }
6799
6800     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6801 }
6802
6803 /* Common tail of UserMoveEvent and DropMenuEvent */
6804 int
6805 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6806 {
6807     char *bookHit = 0;
6808
6809     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6810         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6811         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6812         if(WhiteOnMove(currentMove)) {
6813             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6814         } else {
6815             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6816         }
6817     }
6818
6819     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6820        move type in caller when we know the move is a legal promotion */
6821     if(moveType == NormalMove && promoChar)
6822         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6823
6824     /* [HGM] <popupFix> The following if has been moved here from
6825        UserMoveEvent(). Because it seemed to belong here (why not allow
6826        piece drops in training games?), and because it can only be
6827        performed after it is known to what we promote. */
6828     if (gameMode == Training) {
6829       /* compare the move played on the board to the next move in the
6830        * game. If they match, display the move and the opponent's response.
6831        * If they don't match, display an error message.
6832        */
6833       int saveAnimate;
6834       Board testBoard;
6835       CopyBoard(testBoard, boards[currentMove]);
6836       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6837
6838       if (CompareBoards(testBoard, boards[currentMove+1])) {
6839         ForwardInner(currentMove+1);
6840
6841         /* Autoplay the opponent's response.
6842          * if appData.animate was TRUE when Training mode was entered,
6843          * the response will be animated.
6844          */
6845         saveAnimate = appData.animate;
6846         appData.animate = animateTraining;
6847         ForwardInner(currentMove+1);
6848         appData.animate = saveAnimate;
6849
6850         /* check for the end of the game */
6851         if (currentMove >= forwardMostMove) {
6852           gameMode = PlayFromGameFile;
6853           ModeHighlight();
6854           SetTrainingModeOff();
6855           DisplayInformation(_("End of game"));
6856         }
6857       } else {
6858         DisplayError(_("Incorrect move"), 0);
6859       }
6860       return 1;
6861     }
6862
6863   /* Ok, now we know that the move is good, so we can kill
6864      the previous line in Analysis Mode */
6865   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6866                                 && currentMove < forwardMostMove) {
6867     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6868     else forwardMostMove = currentMove;
6869   }
6870
6871   ClearMap();
6872
6873   /* If we need the chess program but it's dead, restart it */
6874   ResurrectChessProgram();
6875
6876   /* A user move restarts a paused game*/
6877   if (pausing)
6878     PauseEvent();
6879
6880   thinkOutput[0] = NULLCHAR;
6881
6882   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6883
6884   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6885     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6886     return 1;
6887   }
6888
6889   if (gameMode == BeginningOfGame) {
6890     if (appData.noChessProgram) {
6891       gameMode = EditGame;
6892       SetGameInfo();
6893     } else {
6894       char buf[MSG_SIZ];
6895       gameMode = MachinePlaysBlack;
6896       StartClocks();
6897       SetGameInfo();
6898       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6899       DisplayTitle(buf);
6900       if (first.sendName) {
6901         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6902         SendToProgram(buf, &first);
6903       }
6904       StartClocks();
6905     }
6906     ModeHighlight();
6907   }
6908
6909   /* Relay move to ICS or chess engine */
6910   if (appData.icsActive) {
6911     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6912         gameMode == IcsExamining) {
6913       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6914         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6915         SendToICS("draw ");
6916         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6917       }
6918       // also send plain move, in case ICS does not understand atomic claims
6919       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6920       ics_user_moved = 1;
6921     }
6922   } else {
6923     if (first.sendTime && (gameMode == BeginningOfGame ||
6924                            gameMode == MachinePlaysWhite ||
6925                            gameMode == MachinePlaysBlack)) {
6926       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6927     }
6928     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6929          // [HGM] book: if program might be playing, let it use book
6930         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6931         first.maybeThinking = TRUE;
6932     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6933         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6934         SendBoard(&first, currentMove+1);
6935         if(second.analyzing) {
6936             if(!second.useSetboard) SendToProgram("undo\n", &second);
6937             SendBoard(&second, currentMove+1);
6938         }
6939     } else {
6940         SendMoveToProgram(forwardMostMove-1, &first);
6941         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6942     }
6943     if (currentMove == cmailOldMove + 1) {
6944       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6945     }
6946   }
6947
6948   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6949
6950   switch (gameMode) {
6951   case EditGame:
6952     if(appData.testLegality)
6953     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6954     case MT_NONE:
6955     case MT_CHECK:
6956       break;
6957     case MT_CHECKMATE:
6958     case MT_STAINMATE:
6959       if (WhiteOnMove(currentMove)) {
6960         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6961       } else {
6962         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6963       }
6964       break;
6965     case MT_STALEMATE:
6966       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6967       break;
6968     }
6969     break;
6970
6971   case MachinePlaysBlack:
6972   case MachinePlaysWhite:
6973     /* disable certain menu options while machine is thinking */
6974     SetMachineThinkingEnables();
6975     break;
6976
6977   default:
6978     break;
6979   }
6980
6981   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6982   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6983
6984   if(bookHit) { // [HGM] book: simulate book reply
6985         static char bookMove[MSG_SIZ]; // a bit generous?
6986
6987         programStats.nodes = programStats.depth = programStats.time =
6988         programStats.score = programStats.got_only_move = 0;
6989         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6990
6991         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6992         strcat(bookMove, bookHit);
6993         HandleMachineMove(bookMove, &first);
6994   }
6995   return 1;
6996 }
6997
6998 void
6999 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7000 {
7001     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7002     Markers *m = (Markers *) closure;
7003     if(rf == fromY && ff == fromX)
7004         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7005                          || kind == WhiteCapturesEnPassant
7006                          || kind == BlackCapturesEnPassant);
7007     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7008 }
7009
7010 void
7011 MarkTargetSquares (int clear)
7012 {
7013   int x, y;
7014   if(clear) // no reason to ever suppress clearing
7015     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7016   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7017      !appData.testLegality || gameMode == EditPosition) return;
7018   if(!clear) {
7019     int capt = 0;
7020     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7021     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7022       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7023       if(capt)
7024       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7025     }
7026   }
7027   DrawPosition(FALSE, NULL);
7028 }
7029
7030 int
7031 Explode (Board board, int fromX, int fromY, int toX, int toY)
7032 {
7033     if(gameInfo.variant == VariantAtomic &&
7034        (board[toY][toX] != EmptySquare ||                     // capture?
7035         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7036                          board[fromY][fromX] == BlackPawn   )
7037       )) {
7038         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7039         return TRUE;
7040     }
7041     return FALSE;
7042 }
7043
7044 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7045
7046 int
7047 CanPromote (ChessSquare piece, int y)
7048 {
7049         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7050         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7051         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7052            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7053            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7054                                                   gameInfo.variant == VariantMakruk) return FALSE;
7055         return (piece == BlackPawn && y == 1 ||
7056                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7057                 piece == BlackLance && y == 1 ||
7058                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7059 }
7060
7061 void
7062 LeftClick (ClickType clickType, int xPix, int yPix)
7063 {
7064     int x, y;
7065     Boolean saveAnimate;
7066     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7067     char promoChoice = NULLCHAR;
7068     ChessSquare piece;
7069     static TimeMark lastClickTime, prevClickTime;
7070
7071     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7072
7073     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7074
7075     if (clickType == Press) ErrorPopDown();
7076
7077     x = EventToSquare(xPix, BOARD_WIDTH);
7078     y = EventToSquare(yPix, BOARD_HEIGHT);
7079     if (!flipView && y >= 0) {
7080         y = BOARD_HEIGHT - 1 - y;
7081     }
7082     if (flipView && x >= 0) {
7083         x = BOARD_WIDTH - 1 - x;
7084     }
7085
7086     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7087         defaultPromoChoice = promoSweep;
7088         promoSweep = EmptySquare;   // terminate sweep
7089         promoDefaultAltered = TRUE;
7090         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7091     }
7092
7093     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7094         if(clickType == Release) return; // ignore upclick of click-click destination
7095         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7096         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7097         if(gameInfo.holdingsWidth &&
7098                 (WhiteOnMove(currentMove)
7099                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7100                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7101             // click in right holdings, for determining promotion piece
7102             ChessSquare p = boards[currentMove][y][x];
7103             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7104             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7105             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7106                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7107                 fromX = fromY = -1;
7108                 return;
7109             }
7110         }
7111         DrawPosition(FALSE, boards[currentMove]);
7112         return;
7113     }
7114
7115     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7116     if(clickType == Press
7117             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7118               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7119               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7120         return;
7121
7122     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7123         // could be static click on premove from-square: abort premove
7124         gotPremove = 0;
7125         ClearPremoveHighlights();
7126     }
7127
7128     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7129         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7130
7131     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7132         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7133                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7134         defaultPromoChoice = DefaultPromoChoice(side);
7135     }
7136
7137     autoQueen = appData.alwaysPromoteToQueen;
7138
7139     if (fromX == -1) {
7140       int originalY = y;
7141       gatingPiece = EmptySquare;
7142       if (clickType != Press) {
7143         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7144             DragPieceEnd(xPix, yPix); dragging = 0;
7145             DrawPosition(FALSE, NULL);
7146         }
7147         return;
7148       }
7149       doubleClick = FALSE;
7150       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7151         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7152       }
7153       fromX = x; fromY = y; toX = toY = -1;
7154       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7155          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7156          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7157             /* First square */
7158             if (OKToStartUserMove(fromX, fromY)) {
7159                 second = 0;
7160                 MarkTargetSquares(0);
7161                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7162                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7163                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7164                     promoSweep = defaultPromoChoice;
7165                     selectFlag = 0; lastX = xPix; lastY = yPix;
7166                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7167                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7168                 }
7169                 if (appData.highlightDragging) {
7170                     SetHighlights(fromX, fromY, -1, -1);
7171                 } else {
7172                     ClearHighlights();
7173                 }
7174             } else fromX = fromY = -1;
7175             return;
7176         }
7177     }
7178
7179     /* fromX != -1 */
7180     if (clickType == Press && gameMode != EditPosition) {
7181         ChessSquare fromP;
7182         ChessSquare toP;
7183         int frc;
7184
7185         // ignore off-board to clicks
7186         if(y < 0 || x < 0) return;
7187
7188         /* Check if clicking again on the same color piece */
7189         fromP = boards[currentMove][fromY][fromX];
7190         toP = boards[currentMove][y][x];
7191         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7192         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7193              WhitePawn <= toP && toP <= WhiteKing &&
7194              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7195              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7196             (BlackPawn <= fromP && fromP <= BlackKing &&
7197              BlackPawn <= toP && toP <= BlackKing &&
7198              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7199              !(fromP == BlackKing && toP == BlackRook && frc))) {
7200             /* Clicked again on same color piece -- changed his mind */
7201             second = (x == fromX && y == fromY);
7202             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7203                 second = FALSE; // first double-click rather than scond click
7204                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7205             }
7206             promoDefaultAltered = FALSE;
7207             MarkTargetSquares(1);
7208            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7209             if (appData.highlightDragging) {
7210                 SetHighlights(x, y, -1, -1);
7211             } else {
7212                 ClearHighlights();
7213             }
7214             if (OKToStartUserMove(x, y)) {
7215                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7216                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7217                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7218                  gatingPiece = boards[currentMove][fromY][fromX];
7219                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7220                 fromX = x;
7221                 fromY = y; dragging = 1;
7222                 MarkTargetSquares(0);
7223                 DragPieceBegin(xPix, yPix, FALSE);
7224                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7225                     promoSweep = defaultPromoChoice;
7226                     selectFlag = 0; lastX = xPix; lastY = yPix;
7227                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7228                 }
7229             }
7230            }
7231            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7232            second = FALSE;
7233         }
7234         // ignore clicks on holdings
7235         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7236     }
7237
7238     if (clickType == Release && x == fromX && y == fromY) {
7239         DragPieceEnd(xPix, yPix); dragging = 0;
7240         if(clearFlag) {
7241             // a deferred attempt to click-click move an empty square on top of a piece
7242             boards[currentMove][y][x] = EmptySquare;
7243             ClearHighlights();
7244             DrawPosition(FALSE, boards[currentMove]);
7245             fromX = fromY = -1; clearFlag = 0;
7246             return;
7247         }
7248         if (appData.animateDragging) {
7249             /* Undo animation damage if any */
7250             DrawPosition(FALSE, NULL);
7251         }
7252         if (second || sweepSelecting) {
7253             /* Second up/down in same square; just abort move */
7254             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7255             second = sweepSelecting = 0;
7256             fromX = fromY = -1;
7257             gatingPiece = EmptySquare;
7258             ClearHighlights();
7259             gotPremove = 0;
7260             ClearPremoveHighlights();
7261         } else {
7262             /* First upclick in same square; start click-click mode */
7263             SetHighlights(x, y, -1, -1);
7264         }
7265         return;
7266     }
7267
7268     clearFlag = 0;
7269
7270     /* we now have a different from- and (possibly off-board) to-square */
7271     /* Completed move */
7272     if(!sweepSelecting) {
7273         toX = x;
7274         toY = y;
7275     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7276
7277     saveAnimate = appData.animate;
7278     if (clickType == Press) {
7279         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7280             // must be Edit Position mode with empty-square selected
7281             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7282             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7283             return;
7284         }
7285         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7286           if(appData.sweepSelect) {
7287             ChessSquare piece = boards[currentMove][fromY][fromX];
7288             promoSweep = defaultPromoChoice;
7289             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7290             selectFlag = 0; lastX = xPix; lastY = yPix;
7291             Sweep(0); // Pawn that is going to promote: preview promotion piece
7292             sweepSelecting = 1;
7293             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7294             MarkTargetSquares(1);
7295           }
7296           return; // promo popup appears on up-click
7297         }
7298         /* Finish clickclick move */
7299         if (appData.animate || appData.highlightLastMove) {
7300             SetHighlights(fromX, fromY, toX, toY);
7301         } else {
7302             ClearHighlights();
7303         }
7304     } else {
7305 #if 0
7306 // [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
7307         /* Finish drag move */
7308         if (appData.highlightLastMove) {
7309             SetHighlights(fromX, fromY, toX, toY);
7310         } else {
7311             ClearHighlights();
7312         }
7313 #endif
7314         DragPieceEnd(xPix, yPix); dragging = 0;
7315         /* Don't animate move and drag both */
7316         appData.animate = FALSE;
7317     }
7318
7319     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7320     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7321         ChessSquare piece = boards[currentMove][fromY][fromX];
7322         if(gameMode == EditPosition && piece != EmptySquare &&
7323            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7324             int n;
7325
7326             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7327                 n = PieceToNumber(piece - (int)BlackPawn);
7328                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7329                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7330                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7331             } else
7332             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7333                 n = PieceToNumber(piece);
7334                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7335                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7336                 boards[currentMove][n][BOARD_WIDTH-2]++;
7337             }
7338             boards[currentMove][fromY][fromX] = EmptySquare;
7339         }
7340         ClearHighlights();
7341         fromX = fromY = -1;
7342         MarkTargetSquares(1);
7343         DrawPosition(TRUE, boards[currentMove]);
7344         return;
7345     }
7346
7347     // off-board moves should not be highlighted
7348     if(x < 0 || y < 0) ClearHighlights();
7349
7350     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7351
7352     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7353         SetHighlights(fromX, fromY, toX, toY);
7354         MarkTargetSquares(1);
7355         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7356             // [HGM] super: promotion to captured piece selected from holdings
7357             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7358             promotionChoice = TRUE;
7359             // kludge follows to temporarily execute move on display, without promoting yet
7360             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7361             boards[currentMove][toY][toX] = p;
7362             DrawPosition(FALSE, boards[currentMove]);
7363             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7364             boards[currentMove][toY][toX] = q;
7365             DisplayMessage("Click in holdings to choose piece", "");
7366             return;
7367         }
7368         PromotionPopUp();
7369     } else {
7370         int oldMove = currentMove;
7371         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7372         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7373         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7374         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7375            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7376             DrawPosition(TRUE, boards[currentMove]);
7377         MarkTargetSquares(1);
7378         fromX = fromY = -1;
7379     }
7380     appData.animate = saveAnimate;
7381     if (appData.animate || appData.animateDragging) {
7382         /* Undo animation damage if needed */
7383         DrawPosition(FALSE, NULL);
7384     }
7385 }
7386
7387 int
7388 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7389 {   // front-end-free part taken out of PieceMenuPopup
7390     int whichMenu; int xSqr, ySqr;
7391
7392     if(seekGraphUp) { // [HGM] seekgraph
7393         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7394         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7395         return -2;
7396     }
7397
7398     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7399          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7400         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7401         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7402         if(action == Press)   {
7403             originalFlip = flipView;
7404             flipView = !flipView; // temporarily flip board to see game from partners perspective
7405             DrawPosition(TRUE, partnerBoard);
7406             DisplayMessage(partnerStatus, "");
7407             partnerUp = TRUE;
7408         } else if(action == Release) {
7409             flipView = originalFlip;
7410             DrawPosition(TRUE, boards[currentMove]);
7411             partnerUp = FALSE;
7412         }
7413         return -2;
7414     }
7415
7416     xSqr = EventToSquare(x, BOARD_WIDTH);
7417     ySqr = EventToSquare(y, BOARD_HEIGHT);
7418     if (action == Release) {
7419         if(pieceSweep != EmptySquare) {
7420             EditPositionMenuEvent(pieceSweep, toX, toY);
7421             pieceSweep = EmptySquare;
7422         } else UnLoadPV(); // [HGM] pv
7423     }
7424     if (action != Press) return -2; // return code to be ignored
7425     switch (gameMode) {
7426       case IcsExamining:
7427         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7428       case EditPosition:
7429         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7430         if (xSqr < 0 || ySqr < 0) return -1;
7431         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7432         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7433         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7434         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7435         NextPiece(0);
7436         return 2; // grab
7437       case IcsObserving:
7438         if(!appData.icsEngineAnalyze) return -1;
7439       case IcsPlayingWhite:
7440       case IcsPlayingBlack:
7441         if(!appData.zippyPlay) goto noZip;
7442       case AnalyzeMode:
7443       case AnalyzeFile:
7444       case MachinePlaysWhite:
7445       case MachinePlaysBlack:
7446       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7447         if (!appData.dropMenu) {
7448           LoadPV(x, y);
7449           return 2; // flag front-end to grab mouse events
7450         }
7451         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7452            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7453       case EditGame:
7454       noZip:
7455         if (xSqr < 0 || ySqr < 0) return -1;
7456         if (!appData.dropMenu || appData.testLegality &&
7457             gameInfo.variant != VariantBughouse &&
7458             gameInfo.variant != VariantCrazyhouse) return -1;
7459         whichMenu = 1; // drop menu
7460         break;
7461       default:
7462         return -1;
7463     }
7464
7465     if (((*fromX = xSqr) < 0) ||
7466         ((*fromY = ySqr) < 0)) {
7467         *fromX = *fromY = -1;
7468         return -1;
7469     }
7470     if (flipView)
7471       *fromX = BOARD_WIDTH - 1 - *fromX;
7472     else
7473       *fromY = BOARD_HEIGHT - 1 - *fromY;
7474
7475     return whichMenu;
7476 }
7477
7478 void
7479 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7480 {
7481 //    char * hint = lastHint;
7482     FrontEndProgramStats stats;
7483
7484     stats.which = cps == &first ? 0 : 1;
7485     stats.depth = cpstats->depth;
7486     stats.nodes = cpstats->nodes;
7487     stats.score = cpstats->score;
7488     stats.time = cpstats->time;
7489     stats.pv = cpstats->movelist;
7490     stats.hint = lastHint;
7491     stats.an_move_index = 0;
7492     stats.an_move_count = 0;
7493
7494     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7495         stats.hint = cpstats->move_name;
7496         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7497         stats.an_move_count = cpstats->nr_moves;
7498     }
7499
7500     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
7501
7502     SetProgramStats( &stats );
7503 }
7504
7505 void
7506 ClearEngineOutputPane (int which)
7507 {
7508     static FrontEndProgramStats dummyStats;
7509     dummyStats.which = which;
7510     dummyStats.pv = "#";
7511     SetProgramStats( &dummyStats );
7512 }
7513
7514 #define MAXPLAYERS 500
7515
7516 char *
7517 TourneyStandings (int display)
7518 {
7519     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7520     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7521     char result, *p, *names[MAXPLAYERS];
7522
7523     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7524         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7525     names[0] = p = strdup(appData.participants);
7526     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7527
7528     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7529
7530     while(result = appData.results[nr]) {
7531         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7532         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7533         wScore = bScore = 0;
7534         switch(result) {
7535           case '+': wScore = 2; break;
7536           case '-': bScore = 2; break;
7537           case '=': wScore = bScore = 1; break;
7538           case ' ':
7539           case '*': return strdup("busy"); // tourney not finished
7540         }
7541         score[w] += wScore;
7542         score[b] += bScore;
7543         games[w]++;
7544         games[b]++;
7545         nr++;
7546     }
7547     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7548     for(w=0; w<nPlayers; w++) {
7549         bScore = -1;
7550         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7551         ranking[w] = b; points[w] = bScore; score[b] = -2;
7552     }
7553     p = malloc(nPlayers*34+1);
7554     for(w=0; w<nPlayers && w<display; w++)
7555         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7556     free(names[0]);
7557     return p;
7558 }
7559
7560 void
7561 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7562 {       // count all piece types
7563         int p, f, r;
7564         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7565         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7566         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7567                 p = board[r][f];
7568                 pCnt[p]++;
7569                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7570                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7571                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7572                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7573                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7574                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7575         }
7576 }
7577
7578 int
7579 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7580 {
7581         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7582         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7583
7584         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7585         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7586         if(myPawns == 2 && nMine == 3) // KPP
7587             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7588         if(myPawns == 1 && nMine == 2) // KP
7589             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7590         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7591             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7592         if(myPawns) return FALSE;
7593         if(pCnt[WhiteRook+side])
7594             return pCnt[BlackRook-side] ||
7595                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7596                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7597                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7598         if(pCnt[WhiteCannon+side]) {
7599             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7600             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7601         }
7602         if(pCnt[WhiteKnight+side])
7603             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7604         return FALSE;
7605 }
7606
7607 int
7608 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7609 {
7610         VariantClass v = gameInfo.variant;
7611
7612         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7613         if(v == VariantShatranj) return TRUE; // always winnable through baring
7614         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7615         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7616
7617         if(v == VariantXiangqi) {
7618                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7619
7620                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7621                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7622                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7623                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7624                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7625                 if(stale) // we have at least one last-rank P plus perhaps C
7626                     return majors // KPKX
7627                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7628                 else // KCA*E*
7629                     return pCnt[WhiteFerz+side] // KCAK
7630                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7631                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7632                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7633
7634         } else if(v == VariantKnightmate) {
7635                 if(nMine == 1) return FALSE;
7636                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7637         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7638                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7639
7640                 if(nMine == 1) return FALSE; // bare King
7641                 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
7642                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7643                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7644                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7645                 if(pCnt[WhiteKnight+side])
7646                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7647                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7648                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7649                 if(nBishops)
7650                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7651                 if(pCnt[WhiteAlfil+side])
7652                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7653                 if(pCnt[WhiteWazir+side])
7654                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7655         }
7656
7657         return TRUE;
7658 }
7659
7660 int
7661 CompareWithRights (Board b1, Board b2)
7662 {
7663     int rights = 0;
7664     if(!CompareBoards(b1, b2)) return FALSE;
7665     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7666     /* compare castling rights */
7667     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7668            rights++; /* King lost rights, while rook still had them */
7669     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7670         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7671            rights++; /* but at least one rook lost them */
7672     }
7673     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7674            rights++;
7675     if( b1[CASTLING][5] != NoRights ) {
7676         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7677            rights++;
7678     }
7679     return rights == 0;
7680 }
7681
7682 int
7683 Adjudicate (ChessProgramState *cps)
7684 {       // [HGM] some adjudications useful with buggy engines
7685         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7686         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7687         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7688         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7689         int k, drop, count = 0; static int bare = 1;
7690         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7691         Boolean canAdjudicate = !appData.icsActive;
7692
7693         // most tests only when we understand the game, i.e. legality-checking on
7694             if( appData.testLegality )
7695             {   /* [HGM] Some more adjudications for obstinate engines */
7696                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7697                 static int moveCount = 6;
7698                 ChessMove result;
7699                 char *reason = NULL;
7700
7701                 /* Count what is on board. */
7702                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7703
7704                 /* Some material-based adjudications that have to be made before stalemate test */
7705                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7706                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7707                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7708                      if(canAdjudicate && appData.checkMates) {
7709                          if(engineOpponent)
7710                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7711                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7712                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7713                          return 1;
7714                      }
7715                 }
7716
7717                 /* Bare King in Shatranj (loses) or Losers (wins) */
7718                 if( nrW == 1 || nrB == 1) {
7719                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7720                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7721                      if(canAdjudicate && appData.checkMates) {
7722                          if(engineOpponent)
7723                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7724                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7725                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7726                          return 1;
7727                      }
7728                   } else
7729                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7730                   {    /* bare King */
7731                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7732                         if(canAdjudicate && appData.checkMates) {
7733                             /* but only adjudicate if adjudication enabled */
7734                             if(engineOpponent)
7735                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7736                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7737                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7738                             return 1;
7739                         }
7740                   }
7741                 } else bare = 1;
7742
7743
7744             // don't wait for engine to announce game end if we can judge ourselves
7745             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7746               case MT_CHECK:
7747                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7748                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7749                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7750                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7751                             checkCnt++;
7752                         if(checkCnt >= 2) {
7753                             reason = "Xboard adjudication: 3rd check";
7754                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7755                             break;
7756                         }
7757                     }
7758                 }
7759               case MT_NONE:
7760               default:
7761                 break;
7762               case MT_STALEMATE:
7763               case MT_STAINMATE:
7764                 reason = "Xboard adjudication: Stalemate";
7765                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7766                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7767                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7768                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7769                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7770                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7771                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7772                                                                         EP_CHECKMATE : EP_WINS);
7773                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7774                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7775                 }
7776                 break;
7777               case MT_CHECKMATE:
7778                 reason = "Xboard adjudication: Checkmate";
7779                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7780                 if(gameInfo.variant == VariantShogi) {
7781                     if(forwardMostMove > backwardMostMove
7782                        && moveList[forwardMostMove-1][1] == '@'
7783                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7784                         reason = "XBoard adjudication: pawn-drop mate";
7785                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7786                     }
7787                 }
7788                 break;
7789             }
7790
7791                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7792                     case EP_STALEMATE:
7793                         result = GameIsDrawn; break;
7794                     case EP_CHECKMATE:
7795                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7796                     case EP_WINS:
7797                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7798                     default:
7799                         result = EndOfFile;
7800                 }
7801                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7802                     if(engineOpponent)
7803                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7804                     GameEnds( result, reason, GE_XBOARD );
7805                     return 1;
7806                 }
7807
7808                 /* Next absolutely insufficient mating material. */
7809                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7810                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7811                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7812
7813                      /* always flag draws, for judging claims */
7814                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7815
7816                      if(canAdjudicate && appData.materialDraws) {
7817                          /* but only adjudicate them if adjudication enabled */
7818                          if(engineOpponent) {
7819                            SendToProgram("force\n", engineOpponent); // suppress reply
7820                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7821                          }
7822                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7823                          return 1;
7824                      }
7825                 }
7826
7827                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7828                 if(gameInfo.variant == VariantXiangqi ?
7829                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7830                  : nrW + nrB == 4 &&
7831                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7832                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7833                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7834                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7835                    ) ) {
7836                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7837                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7838                           if(engineOpponent) {
7839                             SendToProgram("force\n", engineOpponent); // suppress reply
7840                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7841                           }
7842                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7843                           return 1;
7844                      }
7845                 } else moveCount = 6;
7846             }
7847
7848         // Repetition draws and 50-move rule can be applied independently of legality testing
7849
7850                 /* Check for rep-draws */
7851                 count = 0;
7852                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7853                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7854                 for(k = forwardMostMove-2;
7855                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7856                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7857                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7858                     k-=2)
7859                 {   int rights=0;
7860                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7861                         /* compare castling rights */
7862                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7863                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7864                                 rights++; /* King lost rights, while rook still had them */
7865                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7866                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7867                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7868                                    rights++; /* but at least one rook lost them */
7869                         }
7870                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7871                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7872                                 rights++;
7873                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7874                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7875                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7876                                    rights++;
7877                         }
7878                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7879                             && appData.drawRepeats > 1) {
7880                              /* adjudicate after user-specified nr of repeats */
7881                              int result = GameIsDrawn;
7882                              char *details = "XBoard adjudication: repetition draw";
7883                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7884                                 // [HGM] xiangqi: check for forbidden perpetuals
7885                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7886                                 for(m=forwardMostMove; m>k; m-=2) {
7887                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7888                                         ourPerpetual = 0; // the current mover did not always check
7889                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7890                                         hisPerpetual = 0; // the opponent did not always check
7891                                 }
7892                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7893                                                                         ourPerpetual, hisPerpetual);
7894                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7895                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7896                                     details = "Xboard adjudication: perpetual checking";
7897                                 } else
7898                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7899                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7900                                 } else
7901                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7902                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7903                                         result = BlackWins;
7904                                         details = "Xboard adjudication: repetition";
7905                                     }
7906                                 } else // it must be XQ
7907                                 // Now check for perpetual chases
7908                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7909                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7910                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7911                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7912                                         static char resdet[MSG_SIZ];
7913                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7914                                         details = resdet;
7915                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7916                                     } else
7917                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7918                                         break; // Abort repetition-checking loop.
7919                                 }
7920                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7921                              }
7922                              if(engineOpponent) {
7923                                SendToProgram("force\n", engineOpponent); // suppress reply
7924                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7925                              }
7926                              GameEnds( result, details, GE_XBOARD );
7927                              return 1;
7928                         }
7929                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7930                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7931                     }
7932                 }
7933
7934                 /* Now we test for 50-move draws. Determine ply count */
7935                 count = forwardMostMove;
7936                 /* look for last irreversble move */
7937                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7938                     count--;
7939                 /* if we hit starting position, add initial plies */
7940                 if( count == backwardMostMove )
7941                     count -= initialRulePlies;
7942                 count = forwardMostMove - count;
7943                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7944                         // adjust reversible move counter for checks in Xiangqi
7945                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7946                         if(i < backwardMostMove) i = backwardMostMove;
7947                         while(i <= forwardMostMove) {
7948                                 lastCheck = inCheck; // check evasion does not count
7949                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7950                                 if(inCheck || lastCheck) count--; // check does not count
7951                                 i++;
7952                         }
7953                 }
7954                 if( count >= 100)
7955                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7956                          /* this is used to judge if draw claims are legal */
7957                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7958                          if(engineOpponent) {
7959                            SendToProgram("force\n", engineOpponent); // suppress reply
7960                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7961                          }
7962                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7963                          return 1;
7964                 }
7965
7966                 /* if draw offer is pending, treat it as a draw claim
7967                  * when draw condition present, to allow engines a way to
7968                  * claim draws before making their move to avoid a race
7969                  * condition occurring after their move
7970                  */
7971                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7972                          char *p = NULL;
7973                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7974                              p = "Draw claim: 50-move rule";
7975                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7976                              p = "Draw claim: 3-fold repetition";
7977                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7978                              p = "Draw claim: insufficient mating material";
7979                          if( p != NULL && canAdjudicate) {
7980                              if(engineOpponent) {
7981                                SendToProgram("force\n", engineOpponent); // suppress reply
7982                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7983                              }
7984                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7985                              return 1;
7986                          }
7987                 }
7988
7989                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7990                     if(engineOpponent) {
7991                       SendToProgram("force\n", engineOpponent); // suppress reply
7992                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7993                     }
7994                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7995                     return 1;
7996                 }
7997         return 0;
7998 }
7999
8000 char *
8001 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8002 {   // [HGM] book: this routine intercepts moves to simulate book replies
8003     char *bookHit = NULL;
8004
8005     //first determine if the incoming move brings opponent into his book
8006     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8007         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8008     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8009     if(bookHit != NULL && !cps->bookSuspend) {
8010         // make sure opponent is not going to reply after receiving move to book position
8011         SendToProgram("force\n", cps);
8012         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8013     }
8014     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8015     // now arrange restart after book miss
8016     if(bookHit) {
8017         // after a book hit we never send 'go', and the code after the call to this routine
8018         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8019         char buf[MSG_SIZ], *move = bookHit;
8020         if(cps->useSAN) {
8021             int fromX, fromY, toX, toY;
8022             char promoChar;
8023             ChessMove moveType;
8024             move = buf + 30;
8025             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8026                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8027                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8028                                     PosFlags(forwardMostMove),
8029                                     fromY, fromX, toY, toX, promoChar, move);
8030             } else {
8031                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8032                 bookHit = NULL;
8033             }
8034         }
8035         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8036         SendToProgram(buf, cps);
8037         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8038     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8039         SendToProgram("go\n", cps);
8040         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8041     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8042         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8043             SendToProgram("go\n", cps);
8044         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8045     }
8046     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8047 }
8048
8049 int
8050 LoadError (char *errmess, ChessProgramState *cps)
8051 {   // unloads engine and switches back to -ncp mode if it was first
8052     if(cps->initDone) return FALSE;
8053     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8054     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8055     cps->pr = NoProc;
8056     if(cps == &first) {
8057         appData.noChessProgram = TRUE;
8058         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8059         gameMode = BeginningOfGame; ModeHighlight();
8060         SetNCPMode();
8061     }
8062     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8063     DisplayMessage("", ""); // erase waiting message
8064     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8065     return TRUE;
8066 }
8067
8068 char *savedMessage;
8069 ChessProgramState *savedState;
8070 void
8071 DeferredBookMove (void)
8072 {
8073         if(savedState->lastPing != savedState->lastPong)
8074                     ScheduleDelayedEvent(DeferredBookMove, 10);
8075         else
8076         HandleMachineMove(savedMessage, savedState);
8077 }
8078
8079 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8080 static ChessProgramState *stalledEngine;
8081 static char stashedInputMove[MSG_SIZ];
8082
8083 void
8084 HandleMachineMove (char *message, ChessProgramState *cps)
8085 {
8086     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8087     char realname[MSG_SIZ];
8088     int fromX, fromY, toX, toY;
8089     ChessMove moveType;
8090     char promoChar;
8091     char *p, *pv=buf1;
8092     int machineWhite, oldError;
8093     char *bookHit;
8094
8095     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8096         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8097         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8098             DisplayError(_("Invalid pairing from pairing engine"), 0);
8099             return;
8100         }
8101         pairingReceived = 1;
8102         NextMatchGame();
8103         return; // Skim the pairing messages here.
8104     }
8105
8106     oldError = cps->userError; cps->userError = 0;
8107
8108 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8109     /*
8110      * Kludge to ignore BEL characters
8111      */
8112     while (*message == '\007') message++;
8113
8114     /*
8115      * [HGM] engine debug message: ignore lines starting with '#' character
8116      */
8117     if(cps->debug && *message == '#') return;
8118
8119     /*
8120      * Look for book output
8121      */
8122     if (cps == &first && bookRequested) {
8123         if (message[0] == '\t' || message[0] == ' ') {
8124             /* Part of the book output is here; append it */
8125             strcat(bookOutput, message);
8126             strcat(bookOutput, "  \n");
8127             return;
8128         } else if (bookOutput[0] != NULLCHAR) {
8129             /* All of book output has arrived; display it */
8130             char *p = bookOutput;
8131             while (*p != NULLCHAR) {
8132                 if (*p == '\t') *p = ' ';
8133                 p++;
8134             }
8135             DisplayInformation(bookOutput);
8136             bookRequested = FALSE;
8137             /* Fall through to parse the current output */
8138         }
8139     }
8140
8141     /*
8142      * Look for machine move.
8143      */
8144     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8145         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8146     {
8147         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8148             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8149             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8150             stalledEngine = cps;
8151             if(appData.ponderNextMove) { // bring opponent out of ponder
8152                 if(gameMode == TwoMachinesPlay) {
8153                     if(cps->other->pause)
8154                         PauseEngine(cps->other);
8155                     else
8156                         SendToProgram("easy\n", cps->other);
8157                 }
8158             }
8159             StopClocks();
8160             return;
8161         }
8162
8163         /* This method is only useful on engines that support ping */
8164         if (cps->lastPing != cps->lastPong) {
8165           if (gameMode == BeginningOfGame) {
8166             /* Extra move from before last new; ignore */
8167             if (appData.debugMode) {
8168                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8169             }
8170           } else {
8171             if (appData.debugMode) {
8172                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8173                         cps->which, gameMode);
8174             }
8175
8176             SendToProgram("undo\n", cps);
8177           }
8178           return;
8179         }
8180
8181         switch (gameMode) {
8182           case BeginningOfGame:
8183             /* Extra move from before last reset; ignore */
8184             if (appData.debugMode) {
8185                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8186             }
8187             return;
8188
8189           case EndOfGame:
8190           case IcsIdle:
8191           default:
8192             /* Extra move after we tried to stop.  The mode test is
8193                not a reliable way of detecting this problem, but it's
8194                the best we can do on engines that don't support ping.
8195             */
8196             if (appData.debugMode) {
8197                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8198                         cps->which, gameMode);
8199             }
8200             SendToProgram("undo\n", cps);
8201             return;
8202
8203           case MachinePlaysWhite:
8204           case IcsPlayingWhite:
8205             machineWhite = TRUE;
8206             break;
8207
8208           case MachinePlaysBlack:
8209           case IcsPlayingBlack:
8210             machineWhite = FALSE;
8211             break;
8212
8213           case TwoMachinesPlay:
8214             machineWhite = (cps->twoMachinesColor[0] == 'w');
8215             break;
8216         }
8217         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8218             if (appData.debugMode) {
8219                 fprintf(debugFP,
8220                         "Ignoring move out of turn by %s, gameMode %d"
8221                         ", forwardMost %d\n",
8222                         cps->which, gameMode, forwardMostMove);
8223             }
8224             return;
8225         }
8226
8227         if(cps->alphaRank) AlphaRank(machineMove, 4);
8228         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8229                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8230             /* Machine move could not be parsed; ignore it. */
8231           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8232                     machineMove, _(cps->which));
8233             DisplayMoveError(buf1);
8234             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8235                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8236             if (gameMode == TwoMachinesPlay) {
8237               GameEnds(machineWhite ? BlackWins : WhiteWins,
8238                        buf1, GE_XBOARD);
8239             }
8240             return;
8241         }
8242
8243         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8244         /* So we have to redo legality test with true e.p. status here,  */
8245         /* to make sure an illegal e.p. capture does not slip through,   */
8246         /* to cause a forfeit on a justified illegal-move complaint      */
8247         /* of the opponent.                                              */
8248         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8249            ChessMove moveType;
8250            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8251                              fromY, fromX, toY, toX, promoChar);
8252             if(moveType == IllegalMove) {
8253               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8254                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8255                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8256                            buf1, GE_XBOARD);
8257                 return;
8258            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8259            /* [HGM] Kludge to handle engines that send FRC-style castling
8260               when they shouldn't (like TSCP-Gothic) */
8261            switch(moveType) {
8262              case WhiteASideCastleFR:
8263              case BlackASideCastleFR:
8264                toX+=2;
8265                currentMoveString[2]++;
8266                break;
8267              case WhiteHSideCastleFR:
8268              case BlackHSideCastleFR:
8269                toX--;
8270                currentMoveString[2]--;
8271                break;
8272              default: ; // nothing to do, but suppresses warning of pedantic compilers
8273            }
8274         }
8275         hintRequested = FALSE;
8276         lastHint[0] = NULLCHAR;
8277         bookRequested = FALSE;
8278         /* Program may be pondering now */
8279         cps->maybeThinking = TRUE;
8280         if (cps->sendTime == 2) cps->sendTime = 1;
8281         if (cps->offeredDraw) cps->offeredDraw--;
8282
8283         /* [AS] Save move info*/
8284         pvInfoList[ forwardMostMove ].score = programStats.score;
8285         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8286         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8287
8288         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8289
8290         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8291         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8292             int count = 0;
8293
8294             while( count < adjudicateLossPlies ) {
8295                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8296
8297                 if( count & 1 ) {
8298                     score = -score; /* Flip score for winning side */
8299                 }
8300
8301                 if( score > adjudicateLossThreshold ) {
8302                     break;
8303                 }
8304
8305                 count++;
8306             }
8307
8308             if( count >= adjudicateLossPlies ) {
8309                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8310
8311                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8312                     "Xboard adjudication",
8313                     GE_XBOARD );
8314
8315                 return;
8316             }
8317         }
8318
8319         if(Adjudicate(cps)) {
8320             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8321             return; // [HGM] adjudicate: for all automatic game ends
8322         }
8323
8324 #if ZIPPY
8325         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8326             first.initDone) {
8327           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8328                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8329                 SendToICS("draw ");
8330                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8331           }
8332           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8333           ics_user_moved = 1;
8334           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8335                 char buf[3*MSG_SIZ];
8336
8337                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8338                         programStats.score / 100.,
8339                         programStats.depth,
8340                         programStats.time / 100.,
8341                         (unsigned int)programStats.nodes,
8342                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8343                         programStats.movelist);
8344                 SendToICS(buf);
8345 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8346           }
8347         }
8348 #endif
8349
8350         /* [AS] Clear stats for next move */
8351         ClearProgramStats();
8352         thinkOutput[0] = NULLCHAR;
8353         hiddenThinkOutputState = 0;
8354
8355         bookHit = NULL;
8356         if (gameMode == TwoMachinesPlay) {
8357             /* [HGM] relaying draw offers moved to after reception of move */
8358             /* and interpreting offer as claim if it brings draw condition */
8359             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8360                 SendToProgram("draw\n", cps->other);
8361             }
8362             if (cps->other->sendTime) {
8363                 SendTimeRemaining(cps->other,
8364                                   cps->other->twoMachinesColor[0] == 'w');
8365             }
8366             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8367             if (firstMove && !bookHit) {
8368                 firstMove = FALSE;
8369                 if (cps->other->useColors) {
8370                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8371                 }
8372                 SendToProgram("go\n", cps->other);
8373             }
8374             cps->other->maybeThinking = TRUE;
8375         }
8376
8377         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8378
8379         if (!pausing && appData.ringBellAfterMoves) {
8380             RingBell();
8381         }
8382
8383         /*
8384          * Reenable menu items that were disabled while
8385          * machine was thinking
8386          */
8387         if (gameMode != TwoMachinesPlay)
8388             SetUserThinkingEnables();
8389
8390         // [HGM] book: after book hit opponent has received move and is now in force mode
8391         // force the book reply into it, and then fake that it outputted this move by jumping
8392         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8393         if(bookHit) {
8394                 static char bookMove[MSG_SIZ]; // a bit generous?
8395
8396                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8397                 strcat(bookMove, bookHit);
8398                 message = bookMove;
8399                 cps = cps->other;
8400                 programStats.nodes = programStats.depth = programStats.time =
8401                 programStats.score = programStats.got_only_move = 0;
8402                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8403
8404                 if(cps->lastPing != cps->lastPong) {
8405                     savedMessage = message; // args for deferred call
8406                     savedState = cps;
8407                     ScheduleDelayedEvent(DeferredBookMove, 10);
8408                     return;
8409                 }
8410                 goto FakeBookMove;
8411         }
8412
8413         return;
8414     }
8415
8416     /* Set special modes for chess engines.  Later something general
8417      *  could be added here; for now there is just one kludge feature,
8418      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8419      *  when "xboard" is given as an interactive command.
8420      */
8421     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8422         cps->useSigint = FALSE;
8423         cps->useSigterm = FALSE;
8424     }
8425     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8426       ParseFeatures(message+8, cps);
8427       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8428     }
8429
8430     if ((!appData.testLegality || gameInfo.variant == VariantFairy) &&
8431                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8432       int dummy, s=6; char buf[MSG_SIZ];
8433       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8434       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8435       if(startedFromSetupPosition) return;
8436       ParseFEN(boards[0], &dummy, message+s);
8437       DrawPosition(TRUE, boards[0]);
8438       startedFromSetupPosition = TRUE;
8439       return;
8440     }
8441     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8442      * want this, I was asked to put it in, and obliged.
8443      */
8444     if (!strncmp(message, "setboard ", 9)) {
8445         Board initial_position;
8446
8447         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8448
8449         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8450             DisplayError(_("Bad FEN received from engine"), 0);
8451             return ;
8452         } else {
8453            Reset(TRUE, FALSE);
8454            CopyBoard(boards[0], initial_position);
8455            initialRulePlies = FENrulePlies;
8456            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8457            else gameMode = MachinePlaysBlack;
8458            DrawPosition(FALSE, boards[currentMove]);
8459         }
8460         return;
8461     }
8462
8463     /*
8464      * Look for communication commands
8465      */
8466     if (!strncmp(message, "telluser ", 9)) {
8467         if(message[9] == '\\' && message[10] == '\\')
8468             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8469         PlayTellSound();
8470         DisplayNote(message + 9);
8471         return;
8472     }
8473     if (!strncmp(message, "tellusererror ", 14)) {
8474         cps->userError = 1;
8475         if(message[14] == '\\' && message[15] == '\\')
8476             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8477         PlayTellSound();
8478         DisplayError(message + 14, 0);
8479         return;
8480     }
8481     if (!strncmp(message, "tellopponent ", 13)) {
8482       if (appData.icsActive) {
8483         if (loggedOn) {
8484           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8485           SendToICS(buf1);
8486         }
8487       } else {
8488         DisplayNote(message + 13);
8489       }
8490       return;
8491     }
8492     if (!strncmp(message, "tellothers ", 11)) {
8493       if (appData.icsActive) {
8494         if (loggedOn) {
8495           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8496           SendToICS(buf1);
8497         }
8498       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8499       return;
8500     }
8501     if (!strncmp(message, "tellall ", 8)) {
8502       if (appData.icsActive) {
8503         if (loggedOn) {
8504           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8505           SendToICS(buf1);
8506         }
8507       } else {
8508         DisplayNote(message + 8);
8509       }
8510       return;
8511     }
8512     if (strncmp(message, "warning", 7) == 0) {
8513         /* Undocumented feature, use tellusererror in new code */
8514         DisplayError(message, 0);
8515         return;
8516     }
8517     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8518         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8519         strcat(realname, " query");
8520         AskQuestion(realname, buf2, buf1, cps->pr);
8521         return;
8522     }
8523     /* Commands from the engine directly to ICS.  We don't allow these to be
8524      *  sent until we are logged on. Crafty kibitzes have been known to
8525      *  interfere with the login process.
8526      */
8527     if (loggedOn) {
8528         if (!strncmp(message, "tellics ", 8)) {
8529             SendToICS(message + 8);
8530             SendToICS("\n");
8531             return;
8532         }
8533         if (!strncmp(message, "tellicsnoalias ", 15)) {
8534             SendToICS(ics_prefix);
8535             SendToICS(message + 15);
8536             SendToICS("\n");
8537             return;
8538         }
8539         /* The following are for backward compatibility only */
8540         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8541             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8542             SendToICS(ics_prefix);
8543             SendToICS(message);
8544             SendToICS("\n");
8545             return;
8546         }
8547     }
8548     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8549         return;
8550     }
8551     /*
8552      * If the move is illegal, cancel it and redraw the board.
8553      * Also deal with other error cases.  Matching is rather loose
8554      * here to accommodate engines written before the spec.
8555      */
8556     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8557         strncmp(message, "Error", 5) == 0) {
8558         if (StrStr(message, "name") ||
8559             StrStr(message, "rating") || StrStr(message, "?") ||
8560             StrStr(message, "result") || StrStr(message, "board") ||
8561             StrStr(message, "bk") || StrStr(message, "computer") ||
8562             StrStr(message, "variant") || StrStr(message, "hint") ||
8563             StrStr(message, "random") || StrStr(message, "depth") ||
8564             StrStr(message, "accepted")) {
8565             return;
8566         }
8567         if (StrStr(message, "protover")) {
8568           /* Program is responding to input, so it's apparently done
8569              initializing, and this error message indicates it is
8570              protocol version 1.  So we don't need to wait any longer
8571              for it to initialize and send feature commands. */
8572           FeatureDone(cps, 1);
8573           cps->protocolVersion = 1;
8574           return;
8575         }
8576         cps->maybeThinking = FALSE;
8577
8578         if (StrStr(message, "draw")) {
8579             /* Program doesn't have "draw" command */
8580             cps->sendDrawOffers = 0;
8581             return;
8582         }
8583         if (cps->sendTime != 1 &&
8584             (StrStr(message, "time") || StrStr(message, "otim"))) {
8585           /* Program apparently doesn't have "time" or "otim" command */
8586           cps->sendTime = 0;
8587           return;
8588         }
8589         if (StrStr(message, "analyze")) {
8590             cps->analysisSupport = FALSE;
8591             cps->analyzing = FALSE;
8592 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8593             EditGameEvent(); // [HGM] try to preserve loaded game
8594             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8595             DisplayError(buf2, 0);
8596             return;
8597         }
8598         if (StrStr(message, "(no matching move)st")) {
8599           /* Special kludge for GNU Chess 4 only */
8600           cps->stKludge = TRUE;
8601           SendTimeControl(cps, movesPerSession, timeControl,
8602                           timeIncrement, appData.searchDepth,
8603                           searchTime);
8604           return;
8605         }
8606         if (StrStr(message, "(no matching move)sd")) {
8607           /* Special kludge for GNU Chess 4 only */
8608           cps->sdKludge = TRUE;
8609           SendTimeControl(cps, movesPerSession, timeControl,
8610                           timeIncrement, appData.searchDepth,
8611                           searchTime);
8612           return;
8613         }
8614         if (!StrStr(message, "llegal")) {
8615             return;
8616         }
8617         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8618             gameMode == IcsIdle) return;
8619         if (forwardMostMove <= backwardMostMove) return;
8620         if (pausing) PauseEvent();
8621       if(appData.forceIllegal) {
8622             // [HGM] illegal: machine refused move; force position after move into it
8623           SendToProgram("force\n", cps);
8624           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8625                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8626                 // when black is to move, while there might be nothing on a2 or black
8627                 // might already have the move. So send the board as if white has the move.
8628                 // But first we must change the stm of the engine, as it refused the last move
8629                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8630                 if(WhiteOnMove(forwardMostMove)) {
8631                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8632                     SendBoard(cps, forwardMostMove); // kludgeless board
8633                 } else {
8634                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8635                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8636                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8637                 }
8638           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8639             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8640                  gameMode == TwoMachinesPlay)
8641               SendToProgram("go\n", cps);
8642             return;
8643       } else
8644         if (gameMode == PlayFromGameFile) {
8645             /* Stop reading this game file */
8646             gameMode = EditGame;
8647             ModeHighlight();
8648         }
8649         /* [HGM] illegal-move claim should forfeit game when Xboard */
8650         /* only passes fully legal moves                            */
8651         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8652             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8653                                 "False illegal-move claim", GE_XBOARD );
8654             return; // do not take back move we tested as valid
8655         }
8656         currentMove = forwardMostMove-1;
8657         DisplayMove(currentMove-1); /* before DisplayMoveError */
8658         SwitchClocks(forwardMostMove-1); // [HGM] race
8659         DisplayBothClocks();
8660         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8661                 parseList[currentMove], _(cps->which));
8662         DisplayMoveError(buf1);
8663         DrawPosition(FALSE, boards[currentMove]);
8664
8665         SetUserThinkingEnables();
8666         return;
8667     }
8668     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8669         /* Program has a broken "time" command that
8670            outputs a string not ending in newline.
8671            Don't use it. */
8672         cps->sendTime = 0;
8673     }
8674
8675     /*
8676      * If chess program startup fails, exit with an error message.
8677      * Attempts to recover here are futile. [HGM] Well, we try anyway
8678      */
8679     if ((StrStr(message, "unknown host") != NULL)
8680         || (StrStr(message, "No remote directory") != NULL)
8681         || (StrStr(message, "not found") != NULL)
8682         || (StrStr(message, "No such file") != NULL)
8683         || (StrStr(message, "can't alloc") != NULL)
8684         || (StrStr(message, "Permission denied") != NULL)) {
8685
8686         cps->maybeThinking = FALSE;
8687         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8688                 _(cps->which), cps->program, cps->host, message);
8689         RemoveInputSource(cps->isr);
8690         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8691             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8692             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8693         }
8694         return;
8695     }
8696
8697     /*
8698      * Look for hint output
8699      */
8700     if (sscanf(message, "Hint: %s", buf1) == 1) {
8701         if (cps == &first && hintRequested) {
8702             hintRequested = FALSE;
8703             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8704                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8705                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8706                                     PosFlags(forwardMostMove),
8707                                     fromY, fromX, toY, toX, promoChar, buf1);
8708                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8709                 DisplayInformation(buf2);
8710             } else {
8711                 /* Hint move could not be parsed!? */
8712               snprintf(buf2, sizeof(buf2),
8713                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8714                         buf1, _(cps->which));
8715                 DisplayError(buf2, 0);
8716             }
8717         } else {
8718           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8719         }
8720         return;
8721     }
8722
8723     /*
8724      * Ignore other messages if game is not in progress
8725      */
8726     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8727         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8728
8729     /*
8730      * look for win, lose, draw, or draw offer
8731      */
8732     if (strncmp(message, "1-0", 3) == 0) {
8733         char *p, *q, *r = "";
8734         p = strchr(message, '{');
8735         if (p) {
8736             q = strchr(p, '}');
8737             if (q) {
8738                 *q = NULLCHAR;
8739                 r = p + 1;
8740             }
8741         }
8742         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8743         return;
8744     } else if (strncmp(message, "0-1", 3) == 0) {
8745         char *p, *q, *r = "";
8746         p = strchr(message, '{');
8747         if (p) {
8748             q = strchr(p, '}');
8749             if (q) {
8750                 *q = NULLCHAR;
8751                 r = p + 1;
8752             }
8753         }
8754         /* Kludge for Arasan 4.1 bug */
8755         if (strcmp(r, "Black resigns") == 0) {
8756             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8757             return;
8758         }
8759         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8760         return;
8761     } else if (strncmp(message, "1/2", 3) == 0) {
8762         char *p, *q, *r = "";
8763         p = strchr(message, '{');
8764         if (p) {
8765             q = strchr(p, '}');
8766             if (q) {
8767                 *q = NULLCHAR;
8768                 r = p + 1;
8769             }
8770         }
8771
8772         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8773         return;
8774
8775     } else if (strncmp(message, "White resign", 12) == 0) {
8776         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8777         return;
8778     } else if (strncmp(message, "Black resign", 12) == 0) {
8779         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8780         return;
8781     } else if (strncmp(message, "White matches", 13) == 0 ||
8782                strncmp(message, "Black matches", 13) == 0   ) {
8783         /* [HGM] ignore GNUShogi noises */
8784         return;
8785     } else if (strncmp(message, "White", 5) == 0 &&
8786                message[5] != '(' &&
8787                StrStr(message, "Black") == NULL) {
8788         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8789         return;
8790     } else if (strncmp(message, "Black", 5) == 0 &&
8791                message[5] != '(') {
8792         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8793         return;
8794     } else if (strcmp(message, "resign") == 0 ||
8795                strcmp(message, "computer resigns") == 0) {
8796         switch (gameMode) {
8797           case MachinePlaysBlack:
8798           case IcsPlayingBlack:
8799             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8800             break;
8801           case MachinePlaysWhite:
8802           case IcsPlayingWhite:
8803             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8804             break;
8805           case TwoMachinesPlay:
8806             if (cps->twoMachinesColor[0] == 'w')
8807               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8808             else
8809               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8810             break;
8811           default:
8812             /* can't happen */
8813             break;
8814         }
8815         return;
8816     } else if (strncmp(message, "opponent mates", 14) == 0) {
8817         switch (gameMode) {
8818           case MachinePlaysBlack:
8819           case IcsPlayingBlack:
8820             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8821             break;
8822           case MachinePlaysWhite:
8823           case IcsPlayingWhite:
8824             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8825             break;
8826           case TwoMachinesPlay:
8827             if (cps->twoMachinesColor[0] == 'w')
8828               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8829             else
8830               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8831             break;
8832           default:
8833             /* can't happen */
8834             break;
8835         }
8836         return;
8837     } else if (strncmp(message, "computer mates", 14) == 0) {
8838         switch (gameMode) {
8839           case MachinePlaysBlack:
8840           case IcsPlayingBlack:
8841             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8842             break;
8843           case MachinePlaysWhite:
8844           case IcsPlayingWhite:
8845             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8846             break;
8847           case TwoMachinesPlay:
8848             if (cps->twoMachinesColor[0] == 'w')
8849               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8850             else
8851               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8852             break;
8853           default:
8854             /* can't happen */
8855             break;
8856         }
8857         return;
8858     } else if (strncmp(message, "checkmate", 9) == 0) {
8859         if (WhiteOnMove(forwardMostMove)) {
8860             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8861         } else {
8862             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8863         }
8864         return;
8865     } else if (strstr(message, "Draw") != NULL ||
8866                strstr(message, "game is a draw") != NULL) {
8867         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8868         return;
8869     } else if (strstr(message, "offer") != NULL &&
8870                strstr(message, "draw") != NULL) {
8871 #if ZIPPY
8872         if (appData.zippyPlay && first.initDone) {
8873             /* Relay offer to ICS */
8874             SendToICS(ics_prefix);
8875             SendToICS("draw\n");
8876         }
8877 #endif
8878         cps->offeredDraw = 2; /* valid until this engine moves twice */
8879         if (gameMode == TwoMachinesPlay) {
8880             if (cps->other->offeredDraw) {
8881                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8882             /* [HGM] in two-machine mode we delay relaying draw offer      */
8883             /* until after we also have move, to see if it is really claim */
8884             }
8885         } else if (gameMode == MachinePlaysWhite ||
8886                    gameMode == MachinePlaysBlack) {
8887           if (userOfferedDraw) {
8888             DisplayInformation(_("Machine accepts your draw offer"));
8889             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8890           } else {
8891             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8892           }
8893         }
8894     }
8895
8896
8897     /*
8898      * Look for thinking output
8899      */
8900     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8901           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8902                                 ) {
8903         int plylev, mvleft, mvtot, curscore, time;
8904         char mvname[MOVE_LEN];
8905         u64 nodes; // [DM]
8906         char plyext;
8907         int ignore = FALSE;
8908         int prefixHint = FALSE;
8909         mvname[0] = NULLCHAR;
8910
8911         switch (gameMode) {
8912           case MachinePlaysBlack:
8913           case IcsPlayingBlack:
8914             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8915             break;
8916           case MachinePlaysWhite:
8917           case IcsPlayingWhite:
8918             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8919             break;
8920           case AnalyzeMode:
8921           case AnalyzeFile:
8922             break;
8923           case IcsObserving: /* [DM] icsEngineAnalyze */
8924             if (!appData.icsEngineAnalyze) ignore = TRUE;
8925             break;
8926           case TwoMachinesPlay:
8927             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8928                 ignore = TRUE;
8929             }
8930             break;
8931           default:
8932             ignore = TRUE;
8933             break;
8934         }
8935
8936         if (!ignore) {
8937             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8938             buf1[0] = NULLCHAR;
8939             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8940                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8941
8942                 if (plyext != ' ' && plyext != '\t') {
8943                     time *= 100;
8944                 }
8945
8946                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8947                 if( cps->scoreIsAbsolute &&
8948                     ( gameMode == MachinePlaysBlack ||
8949                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8950                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8951                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8952                      !WhiteOnMove(currentMove)
8953                     ) )
8954                 {
8955                     curscore = -curscore;
8956                 }
8957
8958                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8959
8960                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8961                         char buf[MSG_SIZ];
8962                         FILE *f;
8963                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8964                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8965                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8966                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8967                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8968                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8969                                 fclose(f);
8970                         } else DisplayError(_("failed writing PV"), 0);
8971                 }
8972
8973                 tempStats.depth = plylev;
8974                 tempStats.nodes = nodes;
8975                 tempStats.time = time;
8976                 tempStats.score = curscore;
8977                 tempStats.got_only_move = 0;
8978
8979                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8980                         int ticklen;
8981
8982                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8983                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8984                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8985                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8986                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8987                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8988                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8989                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8990                 }
8991
8992                 /* Buffer overflow protection */
8993                 if (pv[0] != NULLCHAR) {
8994                     if (strlen(pv) >= sizeof(tempStats.movelist)
8995                         && appData.debugMode) {
8996                         fprintf(debugFP,
8997                                 "PV is too long; using the first %u bytes.\n",
8998                                 (unsigned) sizeof(tempStats.movelist) - 1);
8999                     }
9000
9001                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9002                 } else {
9003                     sprintf(tempStats.movelist, " no PV\n");
9004                 }
9005
9006                 if (tempStats.seen_stat) {
9007                     tempStats.ok_to_send = 1;
9008                 }
9009
9010                 if (strchr(tempStats.movelist, '(') != NULL) {
9011                     tempStats.line_is_book = 1;
9012                     tempStats.nr_moves = 0;
9013                     tempStats.moves_left = 0;
9014                 } else {
9015                     tempStats.line_is_book = 0;
9016                 }
9017
9018                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9019                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9020
9021                 SendProgramStatsToFrontend( cps, &tempStats );
9022
9023                 /*
9024                     [AS] Protect the thinkOutput buffer from overflow... this
9025                     is only useful if buf1 hasn't overflowed first!
9026                 */
9027                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9028                          plylev,
9029                          (gameMode == TwoMachinesPlay ?
9030                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9031                          ((double) curscore) / 100.0,
9032                          prefixHint ? lastHint : "",
9033                          prefixHint ? " " : "" );
9034
9035                 if( buf1[0] != NULLCHAR ) {
9036                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9037
9038                     if( strlen(pv) > max_len ) {
9039                         if( appData.debugMode) {
9040                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9041                         }
9042                         pv[max_len+1] = '\0';
9043                     }
9044
9045                     strcat( thinkOutput, pv);
9046                 }
9047
9048                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9049                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9050                     DisplayMove(currentMove - 1);
9051                 }
9052                 return;
9053
9054             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9055                 /* crafty (9.25+) says "(only move) <move>"
9056                  * if there is only 1 legal move
9057                  */
9058                 sscanf(p, "(only move) %s", buf1);
9059                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9060                 sprintf(programStats.movelist, "%s (only move)", buf1);
9061                 programStats.depth = 1;
9062                 programStats.nr_moves = 1;
9063                 programStats.moves_left = 1;
9064                 programStats.nodes = 1;
9065                 programStats.time = 1;
9066                 programStats.got_only_move = 1;
9067
9068                 /* Not really, but we also use this member to
9069                    mean "line isn't going to change" (Crafty
9070                    isn't searching, so stats won't change) */
9071                 programStats.line_is_book = 1;
9072
9073                 SendProgramStatsToFrontend( cps, &programStats );
9074
9075                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9076                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9077                     DisplayMove(currentMove - 1);
9078                 }
9079                 return;
9080             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9081                               &time, &nodes, &plylev, &mvleft,
9082                               &mvtot, mvname) >= 5) {
9083                 /* The stat01: line is from Crafty (9.29+) in response
9084                    to the "." command */
9085                 programStats.seen_stat = 1;
9086                 cps->maybeThinking = TRUE;
9087
9088                 if (programStats.got_only_move || !appData.periodicUpdates)
9089                   return;
9090
9091                 programStats.depth = plylev;
9092                 programStats.time = time;
9093                 programStats.nodes = nodes;
9094                 programStats.moves_left = mvleft;
9095                 programStats.nr_moves = mvtot;
9096                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9097                 programStats.ok_to_send = 1;
9098                 programStats.movelist[0] = '\0';
9099
9100                 SendProgramStatsToFrontend( cps, &programStats );
9101
9102                 return;
9103
9104             } else if (strncmp(message,"++",2) == 0) {
9105                 /* Crafty 9.29+ outputs this */
9106                 programStats.got_fail = 2;
9107                 return;
9108
9109             } else if (strncmp(message,"--",2) == 0) {
9110                 /* Crafty 9.29+ outputs this */
9111                 programStats.got_fail = 1;
9112                 return;
9113
9114             } else if (thinkOutput[0] != NULLCHAR &&
9115                        strncmp(message, "    ", 4) == 0) {
9116                 unsigned message_len;
9117
9118                 p = message;
9119                 while (*p && *p == ' ') p++;
9120
9121                 message_len = strlen( p );
9122
9123                 /* [AS] Avoid buffer overflow */
9124                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9125                     strcat(thinkOutput, " ");
9126                     strcat(thinkOutput, p);
9127                 }
9128
9129                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9130                     strcat(programStats.movelist, " ");
9131                     strcat(programStats.movelist, p);
9132                 }
9133
9134                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9135                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9136                     DisplayMove(currentMove - 1);
9137                 }
9138                 return;
9139             }
9140         }
9141         else {
9142             buf1[0] = NULLCHAR;
9143
9144             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9145                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9146             {
9147                 ChessProgramStats cpstats;
9148
9149                 if (plyext != ' ' && plyext != '\t') {
9150                     time *= 100;
9151                 }
9152
9153                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9154                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9155                     curscore = -curscore;
9156                 }
9157
9158                 cpstats.depth = plylev;
9159                 cpstats.nodes = nodes;
9160                 cpstats.time = time;
9161                 cpstats.score = curscore;
9162                 cpstats.got_only_move = 0;
9163                 cpstats.movelist[0] = '\0';
9164
9165                 if (buf1[0] != NULLCHAR) {
9166                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9167                 }
9168
9169                 cpstats.ok_to_send = 0;
9170                 cpstats.line_is_book = 0;
9171                 cpstats.nr_moves = 0;
9172                 cpstats.moves_left = 0;
9173
9174                 SendProgramStatsToFrontend( cps, &cpstats );
9175             }
9176         }
9177     }
9178 }
9179
9180
9181 /* Parse a game score from the character string "game", and
9182    record it as the history of the current game.  The game
9183    score is NOT assumed to start from the standard position.
9184    The display is not updated in any way.
9185    */
9186 void
9187 ParseGameHistory (char *game)
9188 {
9189     ChessMove moveType;
9190     int fromX, fromY, toX, toY, boardIndex;
9191     char promoChar;
9192     char *p, *q;
9193     char buf[MSG_SIZ];
9194
9195     if (appData.debugMode)
9196       fprintf(debugFP, "Parsing game history: %s\n", game);
9197
9198     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9199     gameInfo.site = StrSave(appData.icsHost);
9200     gameInfo.date = PGNDate();
9201     gameInfo.round = StrSave("-");
9202
9203     /* Parse out names of players */
9204     while (*game == ' ') game++;
9205     p = buf;
9206     while (*game != ' ') *p++ = *game++;
9207     *p = NULLCHAR;
9208     gameInfo.white = StrSave(buf);
9209     while (*game == ' ') game++;
9210     p = buf;
9211     while (*game != ' ' && *game != '\n') *p++ = *game++;
9212     *p = NULLCHAR;
9213     gameInfo.black = StrSave(buf);
9214
9215     /* Parse moves */
9216     boardIndex = blackPlaysFirst ? 1 : 0;
9217     yynewstr(game);
9218     for (;;) {
9219         yyboardindex = boardIndex;
9220         moveType = (ChessMove) Myylex();
9221         switch (moveType) {
9222           case IllegalMove:             /* maybe suicide chess, etc. */
9223   if (appData.debugMode) {
9224     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9225     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9226     setbuf(debugFP, NULL);
9227   }
9228           case WhitePromotion:
9229           case BlackPromotion:
9230           case WhiteNonPromotion:
9231           case BlackNonPromotion:
9232           case NormalMove:
9233           case WhiteCapturesEnPassant:
9234           case BlackCapturesEnPassant:
9235           case WhiteKingSideCastle:
9236           case WhiteQueenSideCastle:
9237           case BlackKingSideCastle:
9238           case BlackQueenSideCastle:
9239           case WhiteKingSideCastleWild:
9240           case WhiteQueenSideCastleWild:
9241           case BlackKingSideCastleWild:
9242           case BlackQueenSideCastleWild:
9243           /* PUSH Fabien */
9244           case WhiteHSideCastleFR:
9245           case WhiteASideCastleFR:
9246           case BlackHSideCastleFR:
9247           case BlackASideCastleFR:
9248           /* POP Fabien */
9249             fromX = currentMoveString[0] - AAA;
9250             fromY = currentMoveString[1] - ONE;
9251             toX = currentMoveString[2] - AAA;
9252             toY = currentMoveString[3] - ONE;
9253             promoChar = currentMoveString[4];
9254             break;
9255           case WhiteDrop:
9256           case BlackDrop:
9257             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9258             fromX = moveType == WhiteDrop ?
9259               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9260             (int) CharToPiece(ToLower(currentMoveString[0]));
9261             fromY = DROP_RANK;
9262             toX = currentMoveString[2] - AAA;
9263             toY = currentMoveString[3] - ONE;
9264             promoChar = NULLCHAR;
9265             break;
9266           case AmbiguousMove:
9267             /* bug? */
9268             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9269   if (appData.debugMode) {
9270     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9271     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9272     setbuf(debugFP, NULL);
9273   }
9274             DisplayError(buf, 0);
9275             return;
9276           case ImpossibleMove:
9277             /* bug? */
9278             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9279   if (appData.debugMode) {
9280     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9281     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9282     setbuf(debugFP, NULL);
9283   }
9284             DisplayError(buf, 0);
9285             return;
9286           case EndOfFile:
9287             if (boardIndex < backwardMostMove) {
9288                 /* Oops, gap.  How did that happen? */
9289                 DisplayError(_("Gap in move list"), 0);
9290                 return;
9291             }
9292             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9293             if (boardIndex > forwardMostMove) {
9294                 forwardMostMove = boardIndex;
9295             }
9296             return;
9297           case ElapsedTime:
9298             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9299                 strcat(parseList[boardIndex-1], " ");
9300                 strcat(parseList[boardIndex-1], yy_text);
9301             }
9302             continue;
9303           case Comment:
9304           case PGNTag:
9305           case NAG:
9306           default:
9307             /* ignore */
9308             continue;
9309           case WhiteWins:
9310           case BlackWins:
9311           case GameIsDrawn:
9312           case GameUnfinished:
9313             if (gameMode == IcsExamining) {
9314                 if (boardIndex < backwardMostMove) {
9315                     /* Oops, gap.  How did that happen? */
9316                     return;
9317                 }
9318                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9319                 return;
9320             }
9321             gameInfo.result = moveType;
9322             p = strchr(yy_text, '{');
9323             if (p == NULL) p = strchr(yy_text, '(');
9324             if (p == NULL) {
9325                 p = yy_text;
9326                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9327             } else {
9328                 q = strchr(p, *p == '{' ? '}' : ')');
9329                 if (q != NULL) *q = NULLCHAR;
9330                 p++;
9331             }
9332             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9333             gameInfo.resultDetails = StrSave(p);
9334             continue;
9335         }
9336         if (boardIndex >= forwardMostMove &&
9337             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9338             backwardMostMove = blackPlaysFirst ? 1 : 0;
9339             return;
9340         }
9341         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9342                                  fromY, fromX, toY, toX, promoChar,
9343                                  parseList[boardIndex]);
9344         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9345         /* currentMoveString is set as a side-effect of yylex */
9346         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9347         strcat(moveList[boardIndex], "\n");
9348         boardIndex++;
9349         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9350         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9351           case MT_NONE:
9352           case MT_STALEMATE:
9353           default:
9354             break;
9355           case MT_CHECK:
9356             if(gameInfo.variant != VariantShogi)
9357                 strcat(parseList[boardIndex - 1], "+");
9358             break;
9359           case MT_CHECKMATE:
9360           case MT_STAINMATE:
9361             strcat(parseList[boardIndex - 1], "#");
9362             break;
9363         }
9364     }
9365 }
9366
9367
9368 /* Apply a move to the given board  */
9369 void
9370 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9371 {
9372   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9373   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9374
9375     /* [HGM] compute & store e.p. status and castling rights for new position */
9376     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9377
9378       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9379       oldEP = (signed char)board[EP_STATUS];
9380       board[EP_STATUS] = EP_NONE;
9381
9382   if (fromY == DROP_RANK) {
9383         /* must be first */
9384         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9385             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9386             return;
9387         }
9388         piece = board[toY][toX] = (ChessSquare) fromX;
9389   } else {
9390       int i;
9391
9392       if( board[toY][toX] != EmptySquare )
9393            board[EP_STATUS] = EP_CAPTURE;
9394
9395       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9396            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9397                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9398       } else
9399       if( board[fromY][fromX] == WhitePawn ) {
9400            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9401                board[EP_STATUS] = EP_PAWN_MOVE;
9402            if( toY-fromY==2) {
9403                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9404                         gameInfo.variant != VariantBerolina || toX < fromX)
9405                       board[EP_STATUS] = toX | berolina;
9406                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9407                         gameInfo.variant != VariantBerolina || toX > fromX)
9408                       board[EP_STATUS] = toX;
9409            }
9410       } else
9411       if( board[fromY][fromX] == BlackPawn ) {
9412            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9413                board[EP_STATUS] = EP_PAWN_MOVE;
9414            if( toY-fromY== -2) {
9415                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9416                         gameInfo.variant != VariantBerolina || toX < fromX)
9417                       board[EP_STATUS] = toX | berolina;
9418                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9419                         gameInfo.variant != VariantBerolina || toX > fromX)
9420                       board[EP_STATUS] = toX;
9421            }
9422        }
9423
9424        for(i=0; i<nrCastlingRights; i++) {
9425            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9426               board[CASTLING][i] == toX   && castlingRank[i] == toY
9427              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9428        }
9429
9430        if(gameInfo.variant == VariantSChess) { // update virginity
9431            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9432            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9433            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9434            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9435        }
9436
9437      if (fromX == toX && fromY == toY) return;
9438
9439      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9440      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9441      if(gameInfo.variant == VariantKnightmate)
9442          king += (int) WhiteUnicorn - (int) WhiteKing;
9443
9444     /* Code added by Tord: */
9445     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9446     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9447         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9448       board[fromY][fromX] = EmptySquare;
9449       board[toY][toX] = EmptySquare;
9450       if((toX > fromX) != (piece == WhiteRook)) {
9451         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9452       } else {
9453         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9454       }
9455     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9456                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9457       board[fromY][fromX] = EmptySquare;
9458       board[toY][toX] = EmptySquare;
9459       if((toX > fromX) != (piece == BlackRook)) {
9460         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9461       } else {
9462         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9463       }
9464     /* End of code added by Tord */
9465
9466     } else if (board[fromY][fromX] == king
9467         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9468         && toY == fromY && toX > fromX+1) {
9469         board[fromY][fromX] = EmptySquare;
9470         board[toY][toX] = king;
9471         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9472         board[fromY][BOARD_RGHT-1] = EmptySquare;
9473     } else if (board[fromY][fromX] == king
9474         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9475                && toY == fromY && toX < fromX-1) {
9476         board[fromY][fromX] = EmptySquare;
9477         board[toY][toX] = king;
9478         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9479         board[fromY][BOARD_LEFT] = EmptySquare;
9480     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9481                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9482                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9483                ) {
9484         /* white pawn promotion */
9485         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9486         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9487             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9488         board[fromY][fromX] = EmptySquare;
9489     } else if ((fromY >= BOARD_HEIGHT>>1)
9490                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9491                && (toX != fromX)
9492                && gameInfo.variant != VariantXiangqi
9493                && gameInfo.variant != VariantBerolina
9494                && (board[fromY][fromX] == WhitePawn)
9495                && (board[toY][toX] == EmptySquare)) {
9496         board[fromY][fromX] = EmptySquare;
9497         board[toY][toX] = WhitePawn;
9498         captured = board[toY - 1][toX];
9499         board[toY - 1][toX] = EmptySquare;
9500     } else if ((fromY == BOARD_HEIGHT-4)
9501                && (toX == fromX)
9502                && gameInfo.variant == VariantBerolina
9503                && (board[fromY][fromX] == WhitePawn)
9504                && (board[toY][toX] == EmptySquare)) {
9505         board[fromY][fromX] = EmptySquare;
9506         board[toY][toX] = WhitePawn;
9507         if(oldEP & EP_BEROLIN_A) {
9508                 captured = board[fromY][fromX-1];
9509                 board[fromY][fromX-1] = EmptySquare;
9510         }else{  captured = board[fromY][fromX+1];
9511                 board[fromY][fromX+1] = EmptySquare;
9512         }
9513     } else if (board[fromY][fromX] == king
9514         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9515                && toY == fromY && toX > fromX+1) {
9516         board[fromY][fromX] = EmptySquare;
9517         board[toY][toX] = king;
9518         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9519         board[fromY][BOARD_RGHT-1] = EmptySquare;
9520     } else if (board[fromY][fromX] == king
9521         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9522                && toY == fromY && toX < fromX-1) {
9523         board[fromY][fromX] = EmptySquare;
9524         board[toY][toX] = king;
9525         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9526         board[fromY][BOARD_LEFT] = EmptySquare;
9527     } else if (fromY == 7 && fromX == 3
9528                && board[fromY][fromX] == BlackKing
9529                && toY == 7 && toX == 5) {
9530         board[fromY][fromX] = EmptySquare;
9531         board[toY][toX] = BlackKing;
9532         board[fromY][7] = EmptySquare;
9533         board[toY][4] = BlackRook;
9534     } else if (fromY == 7 && fromX == 3
9535                && board[fromY][fromX] == BlackKing
9536                && toY == 7 && toX == 1) {
9537         board[fromY][fromX] = EmptySquare;
9538         board[toY][toX] = BlackKing;
9539         board[fromY][0] = EmptySquare;
9540         board[toY][2] = BlackRook;
9541     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9542                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9543                && toY < promoRank && promoChar
9544                ) {
9545         /* black pawn promotion */
9546         board[toY][toX] = CharToPiece(ToLower(promoChar));
9547         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9548             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9549         board[fromY][fromX] = EmptySquare;
9550     } else if ((fromY < BOARD_HEIGHT>>1)
9551                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9552                && (toX != fromX)
9553                && gameInfo.variant != VariantXiangqi
9554                && gameInfo.variant != VariantBerolina
9555                && (board[fromY][fromX] == BlackPawn)
9556                && (board[toY][toX] == EmptySquare)) {
9557         board[fromY][fromX] = EmptySquare;
9558         board[toY][toX] = BlackPawn;
9559         captured = board[toY + 1][toX];
9560         board[toY + 1][toX] = EmptySquare;
9561     } else if ((fromY == 3)
9562                && (toX == fromX)
9563                && gameInfo.variant == VariantBerolina
9564                && (board[fromY][fromX] == BlackPawn)
9565                && (board[toY][toX] == EmptySquare)) {
9566         board[fromY][fromX] = EmptySquare;
9567         board[toY][toX] = BlackPawn;
9568         if(oldEP & EP_BEROLIN_A) {
9569                 captured = board[fromY][fromX-1];
9570                 board[fromY][fromX-1] = EmptySquare;
9571         }else{  captured = board[fromY][fromX+1];
9572                 board[fromY][fromX+1] = EmptySquare;
9573         }
9574     } else {
9575         board[toY][toX] = board[fromY][fromX];
9576         board[fromY][fromX] = EmptySquare;
9577     }
9578   }
9579
9580     if (gameInfo.holdingsWidth != 0) {
9581
9582       /* !!A lot more code needs to be written to support holdings  */
9583       /* [HGM] OK, so I have written it. Holdings are stored in the */
9584       /* penultimate board files, so they are automaticlly stored   */
9585       /* in the game history.                                       */
9586       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9587                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9588         /* Delete from holdings, by decreasing count */
9589         /* and erasing image if necessary            */
9590         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9591         if(p < (int) BlackPawn) { /* white drop */
9592              p -= (int)WhitePawn;
9593                  p = PieceToNumber((ChessSquare)p);
9594              if(p >= gameInfo.holdingsSize) p = 0;
9595              if(--board[p][BOARD_WIDTH-2] <= 0)
9596                   board[p][BOARD_WIDTH-1] = EmptySquare;
9597              if((int)board[p][BOARD_WIDTH-2] < 0)
9598                         board[p][BOARD_WIDTH-2] = 0;
9599         } else {                  /* black drop */
9600              p -= (int)BlackPawn;
9601                  p = PieceToNumber((ChessSquare)p);
9602              if(p >= gameInfo.holdingsSize) p = 0;
9603              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9604                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9605              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9606                         board[BOARD_HEIGHT-1-p][1] = 0;
9607         }
9608       }
9609       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9610           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9611         /* [HGM] holdings: Add to holdings, if holdings exist */
9612         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9613                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9614                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9615         }
9616         p = (int) captured;
9617         if (p >= (int) BlackPawn) {
9618           p -= (int)BlackPawn;
9619           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9620                   /* in Shogi restore piece to its original  first */
9621                   captured = (ChessSquare) (DEMOTED captured);
9622                   p = DEMOTED p;
9623           }
9624           p = PieceToNumber((ChessSquare)p);
9625           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9626           board[p][BOARD_WIDTH-2]++;
9627           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9628         } else {
9629           p -= (int)WhitePawn;
9630           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9631                   captured = (ChessSquare) (DEMOTED captured);
9632                   p = DEMOTED p;
9633           }
9634           p = PieceToNumber((ChessSquare)p);
9635           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9636           board[BOARD_HEIGHT-1-p][1]++;
9637           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9638         }
9639       }
9640     } else if (gameInfo.variant == VariantAtomic) {
9641       if (captured != EmptySquare) {
9642         int y, x;
9643         for (y = toY-1; y <= toY+1; y++) {
9644           for (x = toX-1; x <= toX+1; x++) {
9645             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9646                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9647               board[y][x] = EmptySquare;
9648             }
9649           }
9650         }
9651         board[toY][toX] = EmptySquare;
9652       }
9653     }
9654     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9655         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9656     } else
9657     if(promoChar == '+') {
9658         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9659         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9660     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9661         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9662         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9663            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9664         board[toY][toX] = newPiece;
9665     }
9666     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9667                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9668         // [HGM] superchess: take promotion piece out of holdings
9669         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9670         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9671             if(!--board[k][BOARD_WIDTH-2])
9672                 board[k][BOARD_WIDTH-1] = EmptySquare;
9673         } else {
9674             if(!--board[BOARD_HEIGHT-1-k][1])
9675                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9676         }
9677     }
9678
9679 }
9680
9681 /* Updates forwardMostMove */
9682 void
9683 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9684 {
9685 //    forwardMostMove++; // [HGM] bare: moved downstream
9686
9687     (void) CoordsToAlgebraic(boards[forwardMostMove],
9688                              PosFlags(forwardMostMove),
9689                              fromY, fromX, toY, toX, promoChar,
9690                              parseList[forwardMostMove]);
9691
9692     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9693         int timeLeft; static int lastLoadFlag=0; int king, piece;
9694         piece = boards[forwardMostMove][fromY][fromX];
9695         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9696         if(gameInfo.variant == VariantKnightmate)
9697             king += (int) WhiteUnicorn - (int) WhiteKing;
9698         if(forwardMostMove == 0) {
9699             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9700                 fprintf(serverMoves, "%s;", UserName());
9701             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9702                 fprintf(serverMoves, "%s;", second.tidy);
9703             fprintf(serverMoves, "%s;", first.tidy);
9704             if(gameMode == MachinePlaysWhite)
9705                 fprintf(serverMoves, "%s;", UserName());
9706             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9707                 fprintf(serverMoves, "%s;", second.tidy);
9708         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9709         lastLoadFlag = loadFlag;
9710         // print base move
9711         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9712         // print castling suffix
9713         if( toY == fromY && piece == king ) {
9714             if(toX-fromX > 1)
9715                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9716             if(fromX-toX >1)
9717                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9718         }
9719         // e.p. suffix
9720         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9721              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9722              boards[forwardMostMove][toY][toX] == EmptySquare
9723              && fromX != toX && fromY != toY)
9724                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9725         // promotion suffix
9726         if(promoChar != NULLCHAR) {
9727             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9728                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9729                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9730             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9731         }
9732         if(!loadFlag) {
9733                 char buf[MOVE_LEN*2], *p; int len;
9734             fprintf(serverMoves, "/%d/%d",
9735                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9736             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9737             else                      timeLeft = blackTimeRemaining/1000;
9738             fprintf(serverMoves, "/%d", timeLeft);
9739                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9740                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9741                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9742                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9743             fprintf(serverMoves, "/%s", buf);
9744         }
9745         fflush(serverMoves);
9746     }
9747
9748     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9749         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9750       return;
9751     }
9752     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9753     if (commentList[forwardMostMove+1] != NULL) {
9754         free(commentList[forwardMostMove+1]);
9755         commentList[forwardMostMove+1] = NULL;
9756     }
9757     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9758     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9759     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9760     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9761     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9762     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9763     adjustedClock = FALSE;
9764     gameInfo.result = GameUnfinished;
9765     if (gameInfo.resultDetails != NULL) {
9766         free(gameInfo.resultDetails);
9767         gameInfo.resultDetails = NULL;
9768     }
9769     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9770                               moveList[forwardMostMove - 1]);
9771     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9772       case MT_NONE:
9773       case MT_STALEMATE:
9774       default:
9775         break;
9776       case MT_CHECK:
9777         if(gameInfo.variant != VariantShogi)
9778             strcat(parseList[forwardMostMove - 1], "+");
9779         break;
9780       case MT_CHECKMATE:
9781       case MT_STAINMATE:
9782         strcat(parseList[forwardMostMove - 1], "#");
9783         break;
9784     }
9785
9786 }
9787
9788 /* Updates currentMove if not pausing */
9789 void
9790 ShowMove (int fromX, int fromY, int toX, int toY)
9791 {
9792     int instant = (gameMode == PlayFromGameFile) ?
9793         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9794     if(appData.noGUI) return;
9795     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9796         if (!instant) {
9797             if (forwardMostMove == currentMove + 1) {
9798                 AnimateMove(boards[forwardMostMove - 1],
9799                             fromX, fromY, toX, toY);
9800             }
9801         }
9802         currentMove = forwardMostMove;
9803     }
9804
9805     if (instant) return;
9806
9807     DisplayMove(currentMove - 1);
9808     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9809             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9810                 SetHighlights(fromX, fromY, toX, toY);
9811             }
9812     }
9813     DrawPosition(FALSE, boards[currentMove]);
9814     DisplayBothClocks();
9815     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9816 }
9817
9818 void
9819 SendEgtPath (ChessProgramState *cps)
9820 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9821         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9822
9823         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9824
9825         while(*p) {
9826             char c, *q = name+1, *r, *s;
9827
9828             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9829             while(*p && *p != ',') *q++ = *p++;
9830             *q++ = ':'; *q = 0;
9831             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9832                 strcmp(name, ",nalimov:") == 0 ) {
9833                 // take nalimov path from the menu-changeable option first, if it is defined
9834               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9835                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9836             } else
9837             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9838                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9839                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9840                 s = r = StrStr(s, ":") + 1; // beginning of path info
9841                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9842                 c = *r; *r = 0;             // temporarily null-terminate path info
9843                     *--q = 0;               // strip of trailig ':' from name
9844                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9845                 *r = c;
9846                 SendToProgram(buf,cps);     // send egtbpath command for this format
9847             }
9848             if(*p == ',') p++; // read away comma to position for next format name
9849         }
9850 }
9851
9852 void
9853 InitChessProgram (ChessProgramState *cps, int setup)
9854 /* setup needed to setup FRC opening position */
9855 {
9856     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9857     if (appData.noChessProgram) return;
9858     hintRequested = FALSE;
9859     bookRequested = FALSE;
9860
9861     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9862     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9863     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9864     if(cps->memSize) { /* [HGM] memory */
9865       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9866         SendToProgram(buf, cps);
9867     }
9868     SendEgtPath(cps); /* [HGM] EGT */
9869     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9870       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9871         SendToProgram(buf, cps);
9872     }
9873
9874     SendToProgram(cps->initString, cps);
9875     if (gameInfo.variant != VariantNormal &&
9876         gameInfo.variant != VariantLoadable
9877         /* [HGM] also send variant if board size non-standard */
9878         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9879                                             ) {
9880       char *v = VariantName(gameInfo.variant);
9881       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9882         /* [HGM] in protocol 1 we have to assume all variants valid */
9883         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9884         DisplayFatalError(buf, 0, 1);
9885         return;
9886       }
9887
9888       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9889       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9890       if( gameInfo.variant == VariantXiangqi )
9891            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9892       if( gameInfo.variant == VariantShogi )
9893            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9894       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9895            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9896       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9897           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9898            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9899       if( gameInfo.variant == VariantCourier )
9900            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9901       if( gameInfo.variant == VariantSuper )
9902            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9903       if( gameInfo.variant == VariantGreat )
9904            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9905       if( gameInfo.variant == VariantSChess )
9906            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9907       if( gameInfo.variant == VariantGrand )
9908            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9909
9910       if(overruled) {
9911         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9912                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9913            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9914            if(StrStr(cps->variants, b) == NULL) {
9915                // specific sized variant not known, check if general sizing allowed
9916                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9917                    if(StrStr(cps->variants, "boardsize") == NULL) {
9918                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9919                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9920                        DisplayFatalError(buf, 0, 1);
9921                        return;
9922                    }
9923                    /* [HGM] here we really should compare with the maximum supported board size */
9924                }
9925            }
9926       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9927       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9928       SendToProgram(buf, cps);
9929     }
9930     currentlyInitializedVariant = gameInfo.variant;
9931
9932     /* [HGM] send opening position in FRC to first engine */
9933     if(setup) {
9934           SendToProgram("force\n", cps);
9935           SendBoard(cps, 0);
9936           /* engine is now in force mode! Set flag to wake it up after first move. */
9937           setboardSpoiledMachineBlack = 1;
9938     }
9939
9940     if (cps->sendICS) {
9941       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9942       SendToProgram(buf, cps);
9943     }
9944     cps->maybeThinking = FALSE;
9945     cps->offeredDraw = 0;
9946     if (!appData.icsActive) {
9947         SendTimeControl(cps, movesPerSession, timeControl,
9948                         timeIncrement, appData.searchDepth,
9949                         searchTime);
9950     }
9951     if (appData.showThinking
9952         // [HGM] thinking: four options require thinking output to be sent
9953         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9954                                 ) {
9955         SendToProgram("post\n", cps);
9956     }
9957     SendToProgram("hard\n", cps);
9958     if (!appData.ponderNextMove) {
9959         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9960            it without being sure what state we are in first.  "hard"
9961            is not a toggle, so that one is OK.
9962          */
9963         SendToProgram("easy\n", cps);
9964     }
9965     if (cps->usePing) {
9966       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9967       SendToProgram(buf, cps);
9968     }
9969     cps->initDone = TRUE;
9970     ClearEngineOutputPane(cps == &second);
9971 }
9972
9973
9974 void
9975 ResendOptions (ChessProgramState *cps)
9976 { // send the stored value of the options
9977   int i;
9978   char buf[MSG_SIZ];
9979   Option *opt = cps->option;
9980   for(i=0; i<cps->nrOptions; i++, opt++) {
9981       switch(opt->type) {
9982         case Spin:
9983         case Slider:
9984         case CheckBox:
9985             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9986           break;
9987         case ComboBox:
9988           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9989           break;
9990         default:
9991             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
9992           break;
9993         case Button:
9994         case SaveButton:
9995           continue;
9996       }
9997       SendToProgram(buf, cps);
9998   }
9999 }
10000
10001 void
10002 StartChessProgram (ChessProgramState *cps)
10003 {
10004     char buf[MSG_SIZ];
10005     int err;
10006
10007     if (appData.noChessProgram) return;
10008     cps->initDone = FALSE;
10009
10010     if (strcmp(cps->host, "localhost") == 0) {
10011         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10012     } else if (*appData.remoteShell == NULLCHAR) {
10013         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10014     } else {
10015         if (*appData.remoteUser == NULLCHAR) {
10016           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10017                     cps->program);
10018         } else {
10019           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10020                     cps->host, appData.remoteUser, cps->program);
10021         }
10022         err = StartChildProcess(buf, "", &cps->pr);
10023     }
10024
10025     if (err != 0) {
10026       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10027         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10028         if(cps != &first) return;
10029         appData.noChessProgram = TRUE;
10030         ThawUI();
10031         SetNCPMode();
10032 //      DisplayFatalError(buf, err, 1);
10033 //      cps->pr = NoProc;
10034 //      cps->isr = NULL;
10035         return;
10036     }
10037
10038     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10039     if (cps->protocolVersion > 1) {
10040       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10041       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10042         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10043         cps->comboCnt = 0;  //                and values of combo boxes
10044       }
10045       SendToProgram(buf, cps);
10046       if(cps->reload) ResendOptions(cps);
10047     } else {
10048       SendToProgram("xboard\n", cps);
10049     }
10050 }
10051
10052 void
10053 TwoMachinesEventIfReady P((void))
10054 {
10055   static int curMess = 0;
10056   if (first.lastPing != first.lastPong) {
10057     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10058     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10059     return;
10060   }
10061   if (second.lastPing != second.lastPong) {
10062     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10063     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10064     return;
10065   }
10066   DisplayMessage("", ""); curMess = 0;
10067   TwoMachinesEvent();
10068 }
10069
10070 char *
10071 MakeName (char *template)
10072 {
10073     time_t clock;
10074     struct tm *tm;
10075     static char buf[MSG_SIZ];
10076     char *p = buf;
10077     int i;
10078
10079     clock = time((time_t *)NULL);
10080     tm = localtime(&clock);
10081
10082     while(*p++ = *template++) if(p[-1] == '%') {
10083         switch(*template++) {
10084           case 0:   *p = 0; return buf;
10085           case 'Y': i = tm->tm_year+1900; break;
10086           case 'y': i = tm->tm_year-100; break;
10087           case 'M': i = tm->tm_mon+1; break;
10088           case 'd': i = tm->tm_mday; break;
10089           case 'h': i = tm->tm_hour; break;
10090           case 'm': i = tm->tm_min; break;
10091           case 's': i = tm->tm_sec; break;
10092           default:  i = 0;
10093         }
10094         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10095     }
10096     return buf;
10097 }
10098
10099 int
10100 CountPlayers (char *p)
10101 {
10102     int n = 0;
10103     while(p = strchr(p, '\n')) p++, n++; // count participants
10104     return n;
10105 }
10106
10107 FILE *
10108 WriteTourneyFile (char *results, FILE *f)
10109 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10110     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10111     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10112         // create a file with tournament description
10113         fprintf(f, "-participants {%s}\n", appData.participants);
10114         fprintf(f, "-seedBase %d\n", appData.seedBase);
10115         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10116         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10117         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10118         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10119         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10120         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10121         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10122         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10123         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10124         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10125         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10126         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10127         fprintf(f, "-polyglotBook %s\n", appData.polyglotBook);
10128         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10129         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10130         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10131         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10132         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10133         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10134         fprintf(f, "-smpCores %d\n", appData.smpCores);
10135         if(searchTime > 0)
10136                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10137         else {
10138                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10139                 fprintf(f, "-tc %s\n", appData.timeControl);
10140                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10141         }
10142         fprintf(f, "-results \"%s\"\n", results);
10143     }
10144     return f;
10145 }
10146
10147 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10148
10149 void
10150 Substitute (char *participants, int expunge)
10151 {
10152     int i, changed, changes=0, nPlayers=0;
10153     char *p, *q, *r, buf[MSG_SIZ];
10154     if(participants == NULL) return;
10155     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10156     r = p = participants; q = appData.participants;
10157     while(*p && *p == *q) {
10158         if(*p == '\n') r = p+1, nPlayers++;
10159         p++; q++;
10160     }
10161     if(*p) { // difference
10162         while(*p && *p++ != '\n');
10163         while(*q && *q++ != '\n');
10164       changed = nPlayers;
10165         changes = 1 + (strcmp(p, q) != 0);
10166     }
10167     if(changes == 1) { // a single engine mnemonic was changed
10168         q = r; while(*q) nPlayers += (*q++ == '\n');
10169         p = buf; while(*r && (*p = *r++) != '\n') p++;
10170         *p = NULLCHAR;
10171         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10172         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10173         if(mnemonic[i]) { // The substitute is valid
10174             FILE *f;
10175             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10176                 flock(fileno(f), LOCK_EX);
10177                 ParseArgsFromFile(f);
10178                 fseek(f, 0, SEEK_SET);
10179                 FREE(appData.participants); appData.participants = participants;
10180                 if(expunge) { // erase results of replaced engine
10181                     int len = strlen(appData.results), w, b, dummy;
10182                     for(i=0; i<len; i++) {
10183                         Pairing(i, nPlayers, &w, &b, &dummy);
10184                         if((w == changed || b == changed) && appData.results[i] == '*') {
10185                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10186                             fclose(f);
10187                             return;
10188                         }
10189                     }
10190                     for(i=0; i<len; i++) {
10191                         Pairing(i, nPlayers, &w, &b, &dummy);
10192                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10193                     }
10194                 }
10195                 WriteTourneyFile(appData.results, f);
10196                 fclose(f); // release lock
10197                 return;
10198             }
10199         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10200     }
10201     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10202     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10203     free(participants);
10204     return;
10205 }
10206
10207 int
10208 CheckPlayers (char *participants)
10209 {
10210         int i;
10211         char buf[MSG_SIZ], *p;
10212         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10213         while(p = strchr(participants, '\n')) {
10214             *p = NULLCHAR;
10215             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10216             if(!mnemonic[i]) {
10217                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10218                 *p = '\n';
10219                 DisplayError(buf, 0);
10220                 return 1;
10221             }
10222             *p = '\n';
10223             participants = p + 1;
10224         }
10225         return 0;
10226 }
10227
10228 int
10229 CreateTourney (char *name)
10230 {
10231         FILE *f;
10232         if(matchMode && strcmp(name, appData.tourneyFile)) {
10233              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10234         }
10235         if(name[0] == NULLCHAR) {
10236             if(appData.participants[0])
10237                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10238             return 0;
10239         }
10240         f = fopen(name, "r");
10241         if(f) { // file exists
10242             ASSIGN(appData.tourneyFile, name);
10243             ParseArgsFromFile(f); // parse it
10244         } else {
10245             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10246             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10247                 DisplayError(_("Not enough participants"), 0);
10248                 return 0;
10249             }
10250             if(CheckPlayers(appData.participants)) return 0;
10251             ASSIGN(appData.tourneyFile, name);
10252             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10253             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10254         }
10255         fclose(f);
10256         appData.noChessProgram = FALSE;
10257         appData.clockMode = TRUE;
10258         SetGNUMode();
10259         return 1;
10260 }
10261
10262 int
10263 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10264 {
10265     char buf[MSG_SIZ], *p, *q;
10266     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10267     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10268     skip = !all && group[0]; // if group requested, we start in skip mode
10269     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10270         p = names; q = buf; header = 0;
10271         while(*p && *p != '\n') *q++ = *p++;
10272         *q = 0;
10273         if(*p == '\n') p++;
10274         if(buf[0] == '#') {
10275             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10276             depth++; // we must be entering a new group
10277             if(all) continue; // suppress printing group headers when complete list requested
10278             header = 1;
10279             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10280         }
10281         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10282         if(engineList[i]) free(engineList[i]);
10283         engineList[i] = strdup(buf);
10284         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10285         if(engineMnemonic[i]) free(engineMnemonic[i]);
10286         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10287             strcat(buf, " (");
10288             sscanf(q + 8, "%s", buf + strlen(buf));
10289             strcat(buf, ")");
10290         }
10291         engineMnemonic[i] = strdup(buf);
10292         i++;
10293     }
10294     engineList[i] = engineMnemonic[i] = NULL;
10295     return i;
10296 }
10297
10298 // following implemented as macro to avoid type limitations
10299 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10300
10301 void
10302 SwapEngines (int n)
10303 {   // swap settings for first engine and other engine (so far only some selected options)
10304     int h;
10305     char *p;
10306     if(n == 0) return;
10307     SWAP(directory, p)
10308     SWAP(chessProgram, p)
10309     SWAP(isUCI, h)
10310     SWAP(hasOwnBookUCI, h)
10311     SWAP(protocolVersion, h)
10312     SWAP(reuse, h)
10313     SWAP(scoreIsAbsolute, h)
10314     SWAP(timeOdds, h)
10315     SWAP(logo, p)
10316     SWAP(pgnName, p)
10317     SWAP(pvSAN, h)
10318     SWAP(engOptions, p)
10319     SWAP(engInitString, p)
10320     SWAP(computerString, p)
10321     SWAP(features, p)
10322     SWAP(fenOverride, p)
10323     SWAP(NPS, h)
10324     SWAP(accumulateTC, h)
10325     SWAP(host, p)
10326 }
10327
10328 int
10329 GetEngineLine (char *s, int n)
10330 {
10331     int i;
10332     char buf[MSG_SIZ];
10333     extern char *icsNames;
10334     if(!s || !*s) return 0;
10335     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10336     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10337     if(!mnemonic[i]) return 0;
10338     if(n == 11) return 1; // just testing if there was a match
10339     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10340     if(n == 1) SwapEngines(n);
10341     ParseArgsFromString(buf);
10342     if(n == 1) SwapEngines(n);
10343     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10344         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10345         ParseArgsFromString(buf);
10346     }
10347     return 1;
10348 }
10349
10350 int
10351 SetPlayer (int player, char *p)
10352 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10353     int i;
10354     char buf[MSG_SIZ], *engineName;
10355     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10356     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10357     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10358     if(mnemonic[i]) {
10359         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10360         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10361         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10362         ParseArgsFromString(buf);
10363     } else { // no engine with this nickname is installed!
10364         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10365         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10366         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10367         ModeHighlight();
10368         DisplayError(buf, 0);
10369         return 0;
10370     }
10371     free(engineName);
10372     return i;
10373 }
10374
10375 char *recentEngines;
10376
10377 void
10378 RecentEngineEvent (int nr)
10379 {
10380     int n;
10381 //    SwapEngines(1); // bump first to second
10382 //    ReplaceEngine(&second, 1); // and load it there
10383     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10384     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10385     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10386         ReplaceEngine(&first, 0);
10387         FloatToFront(&appData.recentEngineList, command[n]);
10388     }
10389 }
10390
10391 int
10392 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10393 {   // determine players from game number
10394     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10395
10396     if(appData.tourneyType == 0) {
10397         roundsPerCycle = (nPlayers - 1) | 1;
10398         pairingsPerRound = nPlayers / 2;
10399     } else if(appData.tourneyType > 0) {
10400         roundsPerCycle = nPlayers - appData.tourneyType;
10401         pairingsPerRound = appData.tourneyType;
10402     }
10403     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10404     gamesPerCycle = gamesPerRound * roundsPerCycle;
10405     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10406     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10407     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10408     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10409     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10410     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10411
10412     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10413     if(appData.roundSync) *syncInterval = gamesPerRound;
10414
10415     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10416
10417     if(appData.tourneyType == 0) {
10418         if(curPairing == (nPlayers-1)/2 ) {
10419             *whitePlayer = curRound;
10420             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10421         } else {
10422             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10423             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10424             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10425             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10426         }
10427     } else if(appData.tourneyType > 1) {
10428         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10429         *whitePlayer = curRound + appData.tourneyType;
10430     } else if(appData.tourneyType > 0) {
10431         *whitePlayer = curPairing;
10432         *blackPlayer = curRound + appData.tourneyType;
10433     }
10434
10435     // take care of white/black alternation per round.
10436     // For cycles and games this is already taken care of by default, derived from matchGame!
10437     return curRound & 1;
10438 }
10439
10440 int
10441 NextTourneyGame (int nr, int *swapColors)
10442 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10443     char *p, *q;
10444     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10445     FILE *tf;
10446     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10447     tf = fopen(appData.tourneyFile, "r");
10448     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10449     ParseArgsFromFile(tf); fclose(tf);
10450     InitTimeControls(); // TC might be altered from tourney file
10451
10452     nPlayers = CountPlayers(appData.participants); // count participants
10453     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10454     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10455
10456     if(syncInterval) {
10457         p = q = appData.results;
10458         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10459         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10460             DisplayMessage(_("Waiting for other game(s)"),"");
10461             waitingForGame = TRUE;
10462             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10463             return 0;
10464         }
10465         waitingForGame = FALSE;
10466     }
10467
10468     if(appData.tourneyType < 0) {
10469         if(nr>=0 && !pairingReceived) {
10470             char buf[1<<16];
10471             if(pairing.pr == NoProc) {
10472                 if(!appData.pairingEngine[0]) {
10473                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10474                     return 0;
10475                 }
10476                 StartChessProgram(&pairing); // starts the pairing engine
10477             }
10478             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10479             SendToProgram(buf, &pairing);
10480             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10481             SendToProgram(buf, &pairing);
10482             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10483         }
10484         pairingReceived = 0;                              // ... so we continue here
10485         *swapColors = 0;
10486         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10487         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10488         matchGame = 1; roundNr = nr / syncInterval + 1;
10489     }
10490
10491     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10492
10493     // redefine engines, engine dir, etc.
10494     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10495     if(first.pr == NoProc) {
10496       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10497       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10498     }
10499     if(second.pr == NoProc) {
10500       SwapEngines(1);
10501       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10502       SwapEngines(1);         // and make that valid for second engine by swapping
10503       InitEngine(&second, 1);
10504     }
10505     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10506     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10507     return OK;
10508 }
10509
10510 void
10511 NextMatchGame ()
10512 {   // performs game initialization that does not invoke engines, and then tries to start the game
10513     int res, firstWhite, swapColors = 0;
10514     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10515     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
10516         char buf[MSG_SIZ];
10517         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10518         if(strcmp(buf, currentDebugFile)) { // name has changed
10519             FILE *f = fopen(buf, "w");
10520             if(f) { // if opening the new file failed, just keep using the old one
10521                 ASSIGN(currentDebugFile, buf);
10522                 fclose(debugFP);
10523                 debugFP = f;
10524             }
10525             if(appData.serverFileName) {
10526                 if(serverFP) fclose(serverFP);
10527                 serverFP = fopen(appData.serverFileName, "w");
10528                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10529                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10530             }
10531         }
10532     }
10533     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10534     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10535     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10536     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10537     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10538     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10539     Reset(FALSE, first.pr != NoProc);
10540     res = LoadGameOrPosition(matchGame); // setup game
10541     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10542     if(!res) return; // abort when bad game/pos file
10543     TwoMachinesEvent();
10544 }
10545
10546 void
10547 UserAdjudicationEvent (int result)
10548 {
10549     ChessMove gameResult = GameIsDrawn;
10550
10551     if( result > 0 ) {
10552         gameResult = WhiteWins;
10553     }
10554     else if( result < 0 ) {
10555         gameResult = BlackWins;
10556     }
10557
10558     if( gameMode == TwoMachinesPlay ) {
10559         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10560     }
10561 }
10562
10563
10564 // [HGM] save: calculate checksum of game to make games easily identifiable
10565 int
10566 StringCheckSum (char *s)
10567 {
10568         int i = 0;
10569         if(s==NULL) return 0;
10570         while(*s) i = i*259 + *s++;
10571         return i;
10572 }
10573
10574 int
10575 GameCheckSum ()
10576 {
10577         int i, sum=0;
10578         for(i=backwardMostMove; i<forwardMostMove; i++) {
10579                 sum += pvInfoList[i].depth;
10580                 sum += StringCheckSum(parseList[i]);
10581                 sum += StringCheckSum(commentList[i]);
10582                 sum *= 261;
10583         }
10584         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10585         return sum + StringCheckSum(commentList[i]);
10586 } // end of save patch
10587
10588 void
10589 GameEnds (ChessMove result, char *resultDetails, int whosays)
10590 {
10591     GameMode nextGameMode;
10592     int isIcsGame;
10593     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10594
10595     if(endingGame) return; /* [HGM] crash: forbid recursion */
10596     endingGame = 1;
10597     if(twoBoards) { // [HGM] dual: switch back to one board
10598         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10599         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10600     }
10601     if (appData.debugMode) {
10602       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10603               result, resultDetails ? resultDetails : "(null)", whosays);
10604     }
10605
10606     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10607
10608     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10609
10610     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10611         /* If we are playing on ICS, the server decides when the
10612            game is over, but the engine can offer to draw, claim
10613            a draw, or resign.
10614          */
10615 #if ZIPPY
10616         if (appData.zippyPlay && first.initDone) {
10617             if (result == GameIsDrawn) {
10618                 /* In case draw still needs to be claimed */
10619                 SendToICS(ics_prefix);
10620                 SendToICS("draw\n");
10621             } else if (StrCaseStr(resultDetails, "resign")) {
10622                 SendToICS(ics_prefix);
10623                 SendToICS("resign\n");
10624             }
10625         }
10626 #endif
10627         endingGame = 0; /* [HGM] crash */
10628         return;
10629     }
10630
10631     /* If we're loading the game from a file, stop */
10632     if (whosays == GE_FILE) {
10633       (void) StopLoadGameTimer();
10634       gameFileFP = NULL;
10635     }
10636
10637     /* Cancel draw offers */
10638     first.offeredDraw = second.offeredDraw = 0;
10639
10640     /* If this is an ICS game, only ICS can really say it's done;
10641        if not, anyone can. */
10642     isIcsGame = (gameMode == IcsPlayingWhite ||
10643                  gameMode == IcsPlayingBlack ||
10644                  gameMode == IcsObserving    ||
10645                  gameMode == IcsExamining);
10646
10647     if (!isIcsGame || whosays == GE_ICS) {
10648         /* OK -- not an ICS game, or ICS said it was done */
10649         StopClocks();
10650         if (!isIcsGame && !appData.noChessProgram)
10651           SetUserThinkingEnables();
10652
10653         /* [HGM] if a machine claims the game end we verify this claim */
10654         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10655             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10656                 char claimer;
10657                 ChessMove trueResult = (ChessMove) -1;
10658
10659                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10660                                             first.twoMachinesColor[0] :
10661                                             second.twoMachinesColor[0] ;
10662
10663                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10664                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10665                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10666                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10667                 } else
10668                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10669                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10670                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10671                 } else
10672                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10673                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10674                 }
10675
10676                 // now verify win claims, but not in drop games, as we don't understand those yet
10677                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10678                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10679                     (result == WhiteWins && claimer == 'w' ||
10680                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10681                       if (appData.debugMode) {
10682                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10683                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10684                       }
10685                       if(result != trueResult) {
10686                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10687                               result = claimer == 'w' ? BlackWins : WhiteWins;
10688                               resultDetails = buf;
10689                       }
10690                 } else
10691                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10692                     && (forwardMostMove <= backwardMostMove ||
10693                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10694                         (claimer=='b')==(forwardMostMove&1))
10695                                                                                   ) {
10696                       /* [HGM] verify: draws that were not flagged are false claims */
10697                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10698                       result = claimer == 'w' ? BlackWins : WhiteWins;
10699                       resultDetails = buf;
10700                 }
10701                 /* (Claiming a loss is accepted no questions asked!) */
10702             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10703                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10704                 result = GameUnfinished;
10705                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10706             }
10707             /* [HGM] bare: don't allow bare King to win */
10708             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10709                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10710                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10711                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10712                && result != GameIsDrawn)
10713             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10714                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10715                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10716                         if(p >= 0 && p <= (int)WhiteKing) k++;
10717                 }
10718                 if (appData.debugMode) {
10719                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10720                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10721                 }
10722                 if(k <= 1) {
10723                         result = GameIsDrawn;
10724                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10725                         resultDetails = buf;
10726                 }
10727             }
10728         }
10729
10730
10731         if(serverMoves != NULL && !loadFlag) { char c = '=';
10732             if(result==WhiteWins) c = '+';
10733             if(result==BlackWins) c = '-';
10734             if(resultDetails != NULL)
10735                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10736         }
10737         if (resultDetails != NULL) {
10738             gameInfo.result = result;
10739             gameInfo.resultDetails = StrSave(resultDetails);
10740
10741             /* display last move only if game was not loaded from file */
10742             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10743                 DisplayMove(currentMove - 1);
10744
10745             if (forwardMostMove != 0) {
10746                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10747                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10748                                                                 ) {
10749                     if (*appData.saveGameFile != NULLCHAR) {
10750                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10751                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10752                         else
10753                         SaveGameToFile(appData.saveGameFile, TRUE);
10754                     } else if (appData.autoSaveGames) {
10755                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10756                     }
10757                     if (*appData.savePositionFile != NULLCHAR) {
10758                         SavePositionToFile(appData.savePositionFile);
10759                     }
10760                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10761                 }
10762             }
10763
10764             /* Tell program how game ended in case it is learning */
10765             /* [HGM] Moved this to after saving the PGN, just in case */
10766             /* engine died and we got here through time loss. In that */
10767             /* case we will get a fatal error writing the pipe, which */
10768             /* would otherwise lose us the PGN.                       */
10769             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10770             /* output during GameEnds should never be fatal anymore   */
10771             if (gameMode == MachinePlaysWhite ||
10772                 gameMode == MachinePlaysBlack ||
10773                 gameMode == TwoMachinesPlay ||
10774                 gameMode == IcsPlayingWhite ||
10775                 gameMode == IcsPlayingBlack ||
10776                 gameMode == BeginningOfGame) {
10777                 char buf[MSG_SIZ];
10778                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10779                         resultDetails);
10780                 if (first.pr != NoProc) {
10781                     SendToProgram(buf, &first);
10782                 }
10783                 if (second.pr != NoProc &&
10784                     gameMode == TwoMachinesPlay) {
10785                     SendToProgram(buf, &second);
10786                 }
10787             }
10788         }
10789
10790         if (appData.icsActive) {
10791             if (appData.quietPlay &&
10792                 (gameMode == IcsPlayingWhite ||
10793                  gameMode == IcsPlayingBlack)) {
10794                 SendToICS(ics_prefix);
10795                 SendToICS("set shout 1\n");
10796             }
10797             nextGameMode = IcsIdle;
10798             ics_user_moved = FALSE;
10799             /* clean up premove.  It's ugly when the game has ended and the
10800              * premove highlights are still on the board.
10801              */
10802             if (gotPremove) {
10803               gotPremove = FALSE;
10804               ClearPremoveHighlights();
10805               DrawPosition(FALSE, boards[currentMove]);
10806             }
10807             if (whosays == GE_ICS) {
10808                 switch (result) {
10809                 case WhiteWins:
10810                     if (gameMode == IcsPlayingWhite)
10811                         PlayIcsWinSound();
10812                     else if(gameMode == IcsPlayingBlack)
10813                         PlayIcsLossSound();
10814                     break;
10815                 case BlackWins:
10816                     if (gameMode == IcsPlayingBlack)
10817                         PlayIcsWinSound();
10818                     else if(gameMode == IcsPlayingWhite)
10819                         PlayIcsLossSound();
10820                     break;
10821                 case GameIsDrawn:
10822                     PlayIcsDrawSound();
10823                     break;
10824                 default:
10825                     PlayIcsUnfinishedSound();
10826                 }
10827             }
10828         } else if (gameMode == EditGame ||
10829                    gameMode == PlayFromGameFile ||
10830                    gameMode == AnalyzeMode ||
10831                    gameMode == AnalyzeFile) {
10832             nextGameMode = gameMode;
10833         } else {
10834             nextGameMode = EndOfGame;
10835         }
10836         pausing = FALSE;
10837         ModeHighlight();
10838     } else {
10839         nextGameMode = gameMode;
10840     }
10841
10842     if (appData.noChessProgram) {
10843         gameMode = nextGameMode;
10844         ModeHighlight();
10845         endingGame = 0; /* [HGM] crash */
10846         return;
10847     }
10848
10849     if (first.reuse) {
10850         /* Put first chess program into idle state */
10851         if (first.pr != NoProc &&
10852             (gameMode == MachinePlaysWhite ||
10853              gameMode == MachinePlaysBlack ||
10854              gameMode == TwoMachinesPlay ||
10855              gameMode == IcsPlayingWhite ||
10856              gameMode == IcsPlayingBlack ||
10857              gameMode == BeginningOfGame)) {
10858             SendToProgram("force\n", &first);
10859             if (first.usePing) {
10860               char buf[MSG_SIZ];
10861               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10862               SendToProgram(buf, &first);
10863             }
10864         }
10865     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10866         /* Kill off first chess program */
10867         if (first.isr != NULL)
10868           RemoveInputSource(first.isr);
10869         first.isr = NULL;
10870
10871         if (first.pr != NoProc) {
10872             ExitAnalyzeMode();
10873             DoSleep( appData.delayBeforeQuit );
10874             SendToProgram("quit\n", &first);
10875             DoSleep( appData.delayAfterQuit );
10876             DestroyChildProcess(first.pr, first.useSigterm);
10877             first.reload = TRUE;
10878         }
10879         first.pr = NoProc;
10880     }
10881     if (second.reuse) {
10882         /* Put second chess program into idle state */
10883         if (second.pr != NoProc &&
10884             gameMode == TwoMachinesPlay) {
10885             SendToProgram("force\n", &second);
10886             if (second.usePing) {
10887               char buf[MSG_SIZ];
10888               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10889               SendToProgram(buf, &second);
10890             }
10891         }
10892     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10893         /* Kill off second chess program */
10894         if (second.isr != NULL)
10895           RemoveInputSource(second.isr);
10896         second.isr = NULL;
10897
10898         if (second.pr != NoProc) {
10899             DoSleep( appData.delayBeforeQuit );
10900             SendToProgram("quit\n", &second);
10901             DoSleep( appData.delayAfterQuit );
10902             DestroyChildProcess(second.pr, second.useSigterm);
10903             second.reload = TRUE;
10904         }
10905         second.pr = NoProc;
10906     }
10907
10908     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10909         char resChar = '=';
10910         switch (result) {
10911         case WhiteWins:
10912           resChar = '+';
10913           if (first.twoMachinesColor[0] == 'w') {
10914             first.matchWins++;
10915           } else {
10916             second.matchWins++;
10917           }
10918           break;
10919         case BlackWins:
10920           resChar = '-';
10921           if (first.twoMachinesColor[0] == 'b') {
10922             first.matchWins++;
10923           } else {
10924             second.matchWins++;
10925           }
10926           break;
10927         case GameUnfinished:
10928           resChar = ' ';
10929         default:
10930           break;
10931         }
10932
10933         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10934         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10935             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10936             ReserveGame(nextGame, resChar); // sets nextGame
10937             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10938             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10939         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10940
10941         if (nextGame <= appData.matchGames && !abortMatch) {
10942             gameMode = nextGameMode;
10943             matchGame = nextGame; // this will be overruled in tourney mode!
10944             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10945             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10946             endingGame = 0; /* [HGM] crash */
10947             return;
10948         } else {
10949             gameMode = nextGameMode;
10950             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10951                      first.tidy, second.tidy,
10952                      first.matchWins, second.matchWins,
10953                      appData.matchGames - (first.matchWins + second.matchWins));
10954             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10955             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10956             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10957             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10958                 first.twoMachinesColor = "black\n";
10959                 second.twoMachinesColor = "white\n";
10960             } else {
10961                 first.twoMachinesColor = "white\n";
10962                 second.twoMachinesColor = "black\n";
10963             }
10964         }
10965     }
10966     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10967         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10968       ExitAnalyzeMode();
10969     gameMode = nextGameMode;
10970     ModeHighlight();
10971     endingGame = 0;  /* [HGM] crash */
10972     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10973         if(matchMode == TRUE) { // match through command line: exit with or without popup
10974             if(ranking) {
10975                 ToNrEvent(forwardMostMove);
10976                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10977                 else ExitEvent(0);
10978             } else DisplayFatalError(buf, 0, 0);
10979         } else { // match through menu; just stop, with or without popup
10980             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10981             ModeHighlight();
10982             if(ranking){
10983                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10984             } else DisplayNote(buf);
10985       }
10986       if(ranking) free(ranking);
10987     }
10988 }
10989
10990 /* Assumes program was just initialized (initString sent).
10991    Leaves program in force mode. */
10992 void
10993 FeedMovesToProgram (ChessProgramState *cps, int upto)
10994 {
10995     int i;
10996
10997     if (appData.debugMode)
10998       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10999               startedFromSetupPosition ? "position and " : "",
11000               backwardMostMove, upto, cps->which);
11001     if(currentlyInitializedVariant != gameInfo.variant) {
11002       char buf[MSG_SIZ];
11003         // [HGM] variantswitch: make engine aware of new variant
11004         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11005                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11006         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11007         SendToProgram(buf, cps);
11008         currentlyInitializedVariant = gameInfo.variant;
11009     }
11010     SendToProgram("force\n", cps);
11011     if (startedFromSetupPosition) {
11012         SendBoard(cps, backwardMostMove);
11013     if (appData.debugMode) {
11014         fprintf(debugFP, "feedMoves\n");
11015     }
11016     }
11017     for (i = backwardMostMove; i < upto; i++) {
11018         SendMoveToProgram(i, cps);
11019     }
11020 }
11021
11022
11023 int
11024 ResurrectChessProgram ()
11025 {
11026      /* The chess program may have exited.
11027         If so, restart it and feed it all the moves made so far. */
11028     static int doInit = 0;
11029
11030     if (appData.noChessProgram) return 1;
11031
11032     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11033         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11034         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11035         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11036     } else {
11037         if (first.pr != NoProc) return 1;
11038         StartChessProgram(&first);
11039     }
11040     InitChessProgram(&first, FALSE);
11041     FeedMovesToProgram(&first, currentMove);
11042
11043     if (!first.sendTime) {
11044         /* can't tell gnuchess what its clock should read,
11045            so we bow to its notion. */
11046         ResetClocks();
11047         timeRemaining[0][currentMove] = whiteTimeRemaining;
11048         timeRemaining[1][currentMove] = blackTimeRemaining;
11049     }
11050
11051     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11052                 appData.icsEngineAnalyze) && first.analysisSupport) {
11053       SendToProgram("analyze\n", &first);
11054       first.analyzing = TRUE;
11055     }
11056     return 1;
11057 }
11058
11059 /*
11060  * Button procedures
11061  */
11062 void
11063 Reset (int redraw, int init)
11064 {
11065     int i;
11066
11067     if (appData.debugMode) {
11068         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11069                 redraw, init, gameMode);
11070     }
11071     CleanupTail(); // [HGM] vari: delete any stored variations
11072     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11073     pausing = pauseExamInvalid = FALSE;
11074     startedFromSetupPosition = blackPlaysFirst = FALSE;
11075     firstMove = TRUE;
11076     whiteFlag = blackFlag = FALSE;
11077     userOfferedDraw = FALSE;
11078     hintRequested = bookRequested = FALSE;
11079     first.maybeThinking = FALSE;
11080     second.maybeThinking = FALSE;
11081     first.bookSuspend = FALSE; // [HGM] book
11082     second.bookSuspend = FALSE;
11083     thinkOutput[0] = NULLCHAR;
11084     lastHint[0] = NULLCHAR;
11085     ClearGameInfo(&gameInfo);
11086     gameInfo.variant = StringToVariant(appData.variant);
11087     ics_user_moved = ics_clock_paused = FALSE;
11088     ics_getting_history = H_FALSE;
11089     ics_gamenum = -1;
11090     white_holding[0] = black_holding[0] = NULLCHAR;
11091     ClearProgramStats();
11092     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11093
11094     ResetFrontEnd();
11095     ClearHighlights();
11096     flipView = appData.flipView;
11097     ClearPremoveHighlights();
11098     gotPremove = FALSE;
11099     alarmSounded = FALSE;
11100
11101     GameEnds(EndOfFile, NULL, GE_PLAYER);
11102     if(appData.serverMovesName != NULL) {
11103         /* [HGM] prepare to make moves file for broadcasting */
11104         clock_t t = clock();
11105         if(serverMoves != NULL) fclose(serverMoves);
11106         serverMoves = fopen(appData.serverMovesName, "r");
11107         if(serverMoves != NULL) {
11108             fclose(serverMoves);
11109             /* delay 15 sec before overwriting, so all clients can see end */
11110             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11111         }
11112         serverMoves = fopen(appData.serverMovesName, "w");
11113     }
11114
11115     ExitAnalyzeMode();
11116     gameMode = BeginningOfGame;
11117     ModeHighlight();
11118     if(appData.icsActive) gameInfo.variant = VariantNormal;
11119     currentMove = forwardMostMove = backwardMostMove = 0;
11120     MarkTargetSquares(1);
11121     InitPosition(redraw);
11122     for (i = 0; i < MAX_MOVES; i++) {
11123         if (commentList[i] != NULL) {
11124             free(commentList[i]);
11125             commentList[i] = NULL;
11126         }
11127     }
11128     ResetClocks();
11129     timeRemaining[0][0] = whiteTimeRemaining;
11130     timeRemaining[1][0] = blackTimeRemaining;
11131
11132     if (first.pr == NoProc) {
11133         StartChessProgram(&first);
11134     }
11135     if (init) {
11136             InitChessProgram(&first, startedFromSetupPosition);
11137     }
11138     DisplayTitle("");
11139     DisplayMessage("", "");
11140     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11141     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11142     ClearMap();        // [HGM] exclude: invalidate map
11143 }
11144
11145 void
11146 AutoPlayGameLoop ()
11147 {
11148     for (;;) {
11149         if (!AutoPlayOneMove())
11150           return;
11151         if (matchMode || appData.timeDelay == 0)
11152           continue;
11153         if (appData.timeDelay < 0)
11154           return;
11155         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11156         break;
11157     }
11158 }
11159
11160 void
11161 AnalyzeNextGame()
11162 {
11163     ReloadGame(1); // next game
11164 }
11165
11166 int
11167 AutoPlayOneMove ()
11168 {
11169     int fromX, fromY, toX, toY;
11170
11171     if (appData.debugMode) {
11172       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11173     }
11174
11175     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11176       return FALSE;
11177
11178     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11179       pvInfoList[currentMove].depth = programStats.depth;
11180       pvInfoList[currentMove].score = programStats.score;
11181       pvInfoList[currentMove].time  = 0;
11182       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11183     }
11184
11185     if (currentMove >= forwardMostMove) {
11186       if(gameMode == AnalyzeFile) {
11187           if(appData.loadGameIndex == -1) {
11188             GameEnds(EndOfFile, NULL, GE_FILE);
11189           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11190           } else {
11191           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11192         }
11193       }
11194 //      gameMode = EndOfGame;
11195 //      ModeHighlight();
11196
11197       /* [AS] Clear current move marker at the end of a game */
11198       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11199
11200       return FALSE;
11201     }
11202
11203     toX = moveList[currentMove][2] - AAA;
11204     toY = moveList[currentMove][3] - ONE;
11205
11206     if (moveList[currentMove][1] == '@') {
11207         if (appData.highlightLastMove) {
11208             SetHighlights(-1, -1, toX, toY);
11209         }
11210     } else {
11211         fromX = moveList[currentMove][0] - AAA;
11212         fromY = moveList[currentMove][1] - ONE;
11213
11214         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11215
11216         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11217
11218         if (appData.highlightLastMove) {
11219             SetHighlights(fromX, fromY, toX, toY);
11220         }
11221     }
11222     DisplayMove(currentMove);
11223     SendMoveToProgram(currentMove++, &first);
11224     DisplayBothClocks();
11225     DrawPosition(FALSE, boards[currentMove]);
11226     // [HGM] PV info: always display, routine tests if empty
11227     DisplayComment(currentMove - 1, commentList[currentMove]);
11228     return TRUE;
11229 }
11230
11231
11232 int
11233 LoadGameOneMove (ChessMove readAhead)
11234 {
11235     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11236     char promoChar = NULLCHAR;
11237     ChessMove moveType;
11238     char move[MSG_SIZ];
11239     char *p, *q;
11240
11241     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11242         gameMode != AnalyzeMode && gameMode != Training) {
11243         gameFileFP = NULL;
11244         return FALSE;
11245     }
11246
11247     yyboardindex = forwardMostMove;
11248     if (readAhead != EndOfFile) {
11249       moveType = readAhead;
11250     } else {
11251       if (gameFileFP == NULL)
11252           return FALSE;
11253       moveType = (ChessMove) Myylex();
11254     }
11255
11256     done = FALSE;
11257     switch (moveType) {
11258       case Comment:
11259         if (appData.debugMode)
11260           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11261         p = yy_text;
11262
11263         /* append the comment but don't display it */
11264         AppendComment(currentMove, p, FALSE);
11265         return TRUE;
11266
11267       case WhiteCapturesEnPassant:
11268       case BlackCapturesEnPassant:
11269       case WhitePromotion:
11270       case BlackPromotion:
11271       case WhiteNonPromotion:
11272       case BlackNonPromotion:
11273       case NormalMove:
11274       case WhiteKingSideCastle:
11275       case WhiteQueenSideCastle:
11276       case BlackKingSideCastle:
11277       case BlackQueenSideCastle:
11278       case WhiteKingSideCastleWild:
11279       case WhiteQueenSideCastleWild:
11280       case BlackKingSideCastleWild:
11281       case BlackQueenSideCastleWild:
11282       /* PUSH Fabien */
11283       case WhiteHSideCastleFR:
11284       case WhiteASideCastleFR:
11285       case BlackHSideCastleFR:
11286       case BlackASideCastleFR:
11287       /* POP Fabien */
11288         if (appData.debugMode)
11289           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11290         fromX = currentMoveString[0] - AAA;
11291         fromY = currentMoveString[1] - ONE;
11292         toX = currentMoveString[2] - AAA;
11293         toY = currentMoveString[3] - ONE;
11294         promoChar = currentMoveString[4];
11295         break;
11296
11297       case WhiteDrop:
11298       case BlackDrop:
11299         if (appData.debugMode)
11300           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11301         fromX = moveType == WhiteDrop ?
11302           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11303         (int) CharToPiece(ToLower(currentMoveString[0]));
11304         fromY = DROP_RANK;
11305         toX = currentMoveString[2] - AAA;
11306         toY = currentMoveString[3] - ONE;
11307         break;
11308
11309       case WhiteWins:
11310       case BlackWins:
11311       case GameIsDrawn:
11312       case GameUnfinished:
11313         if (appData.debugMode)
11314           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11315         p = strchr(yy_text, '{');
11316         if (p == NULL) p = strchr(yy_text, '(');
11317         if (p == NULL) {
11318             p = yy_text;
11319             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11320         } else {
11321             q = strchr(p, *p == '{' ? '}' : ')');
11322             if (q != NULL) *q = NULLCHAR;
11323             p++;
11324         }
11325         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11326         GameEnds(moveType, p, GE_FILE);
11327         done = TRUE;
11328         if (cmailMsgLoaded) {
11329             ClearHighlights();
11330             flipView = WhiteOnMove(currentMove);
11331             if (moveType == GameUnfinished) flipView = !flipView;
11332             if (appData.debugMode)
11333               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11334         }
11335         break;
11336
11337       case EndOfFile:
11338         if (appData.debugMode)
11339           fprintf(debugFP, "Parser hit end of file\n");
11340         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11341           case MT_NONE:
11342           case MT_CHECK:
11343             break;
11344           case MT_CHECKMATE:
11345           case MT_STAINMATE:
11346             if (WhiteOnMove(currentMove)) {
11347                 GameEnds(BlackWins, "Black mates", GE_FILE);
11348             } else {
11349                 GameEnds(WhiteWins, "White mates", GE_FILE);
11350             }
11351             break;
11352           case MT_STALEMATE:
11353             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11354             break;
11355         }
11356         done = TRUE;
11357         break;
11358
11359       case MoveNumberOne:
11360         if (lastLoadGameStart == GNUChessGame) {
11361             /* GNUChessGames have numbers, but they aren't move numbers */
11362             if (appData.debugMode)
11363               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11364                       yy_text, (int) moveType);
11365             return LoadGameOneMove(EndOfFile); /* tail recursion */
11366         }
11367         /* else fall thru */
11368
11369       case XBoardGame:
11370       case GNUChessGame:
11371       case PGNTag:
11372         /* Reached start of next game in file */
11373         if (appData.debugMode)
11374           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11375         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11376           case MT_NONE:
11377           case MT_CHECK:
11378             break;
11379           case MT_CHECKMATE:
11380           case MT_STAINMATE:
11381             if (WhiteOnMove(currentMove)) {
11382                 GameEnds(BlackWins, "Black mates", GE_FILE);
11383             } else {
11384                 GameEnds(WhiteWins, "White mates", GE_FILE);
11385             }
11386             break;
11387           case MT_STALEMATE:
11388             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11389             break;
11390         }
11391         done = TRUE;
11392         break;
11393
11394       case PositionDiagram:     /* should not happen; ignore */
11395       case ElapsedTime:         /* ignore */
11396       case NAG:                 /* ignore */
11397         if (appData.debugMode)
11398           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11399                   yy_text, (int) moveType);
11400         return LoadGameOneMove(EndOfFile); /* tail recursion */
11401
11402       case IllegalMove:
11403         if (appData.testLegality) {
11404             if (appData.debugMode)
11405               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11406             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11407                     (forwardMostMove / 2) + 1,
11408                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11409             DisplayError(move, 0);
11410             done = TRUE;
11411         } else {
11412             if (appData.debugMode)
11413               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11414                       yy_text, currentMoveString);
11415             fromX = currentMoveString[0] - AAA;
11416             fromY = currentMoveString[1] - ONE;
11417             toX = currentMoveString[2] - AAA;
11418             toY = currentMoveString[3] - ONE;
11419             promoChar = currentMoveString[4];
11420         }
11421         break;
11422
11423       case AmbiguousMove:
11424         if (appData.debugMode)
11425           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11426         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11427                 (forwardMostMove / 2) + 1,
11428                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11429         DisplayError(move, 0);
11430         done = TRUE;
11431         break;
11432
11433       default:
11434       case ImpossibleMove:
11435         if (appData.debugMode)
11436           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11437         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11438                 (forwardMostMove / 2) + 1,
11439                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11440         DisplayError(move, 0);
11441         done = TRUE;
11442         break;
11443     }
11444
11445     if (done) {
11446         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11447             DrawPosition(FALSE, boards[currentMove]);
11448             DisplayBothClocks();
11449             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11450               DisplayComment(currentMove - 1, commentList[currentMove]);
11451         }
11452         (void) StopLoadGameTimer();
11453         gameFileFP = NULL;
11454         cmailOldMove = forwardMostMove;
11455         return FALSE;
11456     } else {
11457         /* currentMoveString is set as a side-effect of yylex */
11458
11459         thinkOutput[0] = NULLCHAR;
11460         MakeMove(fromX, fromY, toX, toY, promoChar);
11461         currentMove = forwardMostMove;
11462         return TRUE;
11463     }
11464 }
11465
11466 /* Load the nth game from the given file */
11467 int
11468 LoadGameFromFile (char *filename, int n, char *title, int useList)
11469 {
11470     FILE *f;
11471     char buf[MSG_SIZ];
11472
11473     if (strcmp(filename, "-") == 0) {
11474         f = stdin;
11475         title = "stdin";
11476     } else {
11477         f = fopen(filename, "rb");
11478         if (f == NULL) {
11479           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11480             DisplayError(buf, errno);
11481             return FALSE;
11482         }
11483     }
11484     if (fseek(f, 0, 0) == -1) {
11485         /* f is not seekable; probably a pipe */
11486         useList = FALSE;
11487     }
11488     if (useList && n == 0) {
11489         int error = GameListBuild(f);
11490         if (error) {
11491             DisplayError(_("Cannot build game list"), error);
11492         } else if (!ListEmpty(&gameList) &&
11493                    ((ListGame *) gameList.tailPred)->number > 1) {
11494             GameListPopUp(f, title);
11495             return TRUE;
11496         }
11497         GameListDestroy();
11498         n = 1;
11499     }
11500     if (n == 0) n = 1;
11501     return LoadGame(f, n, title, FALSE);
11502 }
11503
11504
11505 void
11506 MakeRegisteredMove ()
11507 {
11508     int fromX, fromY, toX, toY;
11509     char promoChar;
11510     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11511         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11512           case CMAIL_MOVE:
11513           case CMAIL_DRAW:
11514             if (appData.debugMode)
11515               fprintf(debugFP, "Restoring %s for game %d\n",
11516                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11517
11518             thinkOutput[0] = NULLCHAR;
11519             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11520             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11521             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11522             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11523             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11524             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11525             MakeMove(fromX, fromY, toX, toY, promoChar);
11526             ShowMove(fromX, fromY, toX, toY);
11527
11528             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11529               case MT_NONE:
11530               case MT_CHECK:
11531                 break;
11532
11533               case MT_CHECKMATE:
11534               case MT_STAINMATE:
11535                 if (WhiteOnMove(currentMove)) {
11536                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11537                 } else {
11538                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11539                 }
11540                 break;
11541
11542               case MT_STALEMATE:
11543                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11544                 break;
11545             }
11546
11547             break;
11548
11549           case CMAIL_RESIGN:
11550             if (WhiteOnMove(currentMove)) {
11551                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11552             } else {
11553                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11554             }
11555             break;
11556
11557           case CMAIL_ACCEPT:
11558             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11559             break;
11560
11561           default:
11562             break;
11563         }
11564     }
11565
11566     return;
11567 }
11568
11569 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11570 int
11571 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11572 {
11573     int retVal;
11574
11575     if (gameNumber > nCmailGames) {
11576         DisplayError(_("No more games in this message"), 0);
11577         return FALSE;
11578     }
11579     if (f == lastLoadGameFP) {
11580         int offset = gameNumber - lastLoadGameNumber;
11581         if (offset == 0) {
11582             cmailMsg[0] = NULLCHAR;
11583             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11584                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11585                 nCmailMovesRegistered--;
11586             }
11587             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11588             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11589                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11590             }
11591         } else {
11592             if (! RegisterMove()) return FALSE;
11593         }
11594     }
11595
11596     retVal = LoadGame(f, gameNumber, title, useList);
11597
11598     /* Make move registered during previous look at this game, if any */
11599     MakeRegisteredMove();
11600
11601     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11602         commentList[currentMove]
11603           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11604         DisplayComment(currentMove - 1, commentList[currentMove]);
11605     }
11606
11607     return retVal;
11608 }
11609
11610 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11611 int
11612 ReloadGame (int offset)
11613 {
11614     int gameNumber = lastLoadGameNumber + offset;
11615     if (lastLoadGameFP == NULL) {
11616         DisplayError(_("No game has been loaded yet"), 0);
11617         return FALSE;
11618     }
11619     if (gameNumber <= 0) {
11620         DisplayError(_("Can't back up any further"), 0);
11621         return FALSE;
11622     }
11623     if (cmailMsgLoaded) {
11624         return CmailLoadGame(lastLoadGameFP, gameNumber,
11625                              lastLoadGameTitle, lastLoadGameUseList);
11626     } else {
11627         return LoadGame(lastLoadGameFP, gameNumber,
11628                         lastLoadGameTitle, lastLoadGameUseList);
11629     }
11630 }
11631
11632 int keys[EmptySquare+1];
11633
11634 int
11635 PositionMatches (Board b1, Board b2)
11636 {
11637     int r, f, sum=0;
11638     switch(appData.searchMode) {
11639         case 1: return CompareWithRights(b1, b2);
11640         case 2:
11641             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11642                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11643             }
11644             return TRUE;
11645         case 3:
11646             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11647               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11648                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11649             }
11650             return sum==0;
11651         case 4:
11652             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11653                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11654             }
11655             return sum==0;
11656     }
11657     return TRUE;
11658 }
11659
11660 #define Q_PROMO  4
11661 #define Q_EP     3
11662 #define Q_BCASTL 2
11663 #define Q_WCASTL 1
11664
11665 int pieceList[256], quickBoard[256];
11666 ChessSquare pieceType[256] = { EmptySquare };
11667 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11668 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11669 int soughtTotal, turn;
11670 Boolean epOK, flipSearch;
11671
11672 typedef struct {
11673     unsigned char piece, to;
11674 } Move;
11675
11676 #define DSIZE (250000)
11677
11678 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11679 Move *moveDatabase = initialSpace;
11680 unsigned int movePtr, dataSize = DSIZE;
11681
11682 int
11683 MakePieceList (Board board, int *counts)
11684 {
11685     int r, f, n=Q_PROMO, total=0;
11686     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11687     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11688         int sq = f + (r<<4);
11689         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11690             quickBoard[sq] = ++n;
11691             pieceList[n] = sq;
11692             pieceType[n] = board[r][f];
11693             counts[board[r][f]]++;
11694             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11695             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11696             total++;
11697         }
11698     }
11699     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11700     return total;
11701 }
11702
11703 void
11704 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11705 {
11706     int sq = fromX + (fromY<<4);
11707     int piece = quickBoard[sq];
11708     quickBoard[sq] = 0;
11709     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11710     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11711         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11712         moveDatabase[movePtr++].piece = Q_WCASTL;
11713         quickBoard[sq] = piece;
11714         piece = quickBoard[from]; quickBoard[from] = 0;
11715         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11716     } else
11717     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11718         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11719         moveDatabase[movePtr++].piece = Q_BCASTL;
11720         quickBoard[sq] = piece;
11721         piece = quickBoard[from]; quickBoard[from] = 0;
11722         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11723     } else
11724     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11725         quickBoard[(fromY<<4)+toX] = 0;
11726         moveDatabase[movePtr].piece = Q_EP;
11727         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11728         moveDatabase[movePtr].to = sq;
11729     } else
11730     if(promoPiece != pieceType[piece]) {
11731         moveDatabase[movePtr++].piece = Q_PROMO;
11732         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11733     }
11734     moveDatabase[movePtr].piece = piece;
11735     quickBoard[sq] = piece;
11736     movePtr++;
11737 }
11738
11739 int
11740 PackGame (Board board)
11741 {
11742     Move *newSpace = NULL;
11743     moveDatabase[movePtr].piece = 0; // terminate previous game
11744     if(movePtr > dataSize) {
11745         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11746         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11747         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11748         if(newSpace) {
11749             int i;
11750             Move *p = moveDatabase, *q = newSpace;
11751             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11752             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11753             moveDatabase = newSpace;
11754         } else { // calloc failed, we must be out of memory. Too bad...
11755             dataSize = 0; // prevent calloc events for all subsequent games
11756             return 0;     // and signal this one isn't cached
11757         }
11758     }
11759     movePtr++;
11760     MakePieceList(board, counts);
11761     return movePtr;
11762 }
11763
11764 int
11765 QuickCompare (Board board, int *minCounts, int *maxCounts)
11766 {   // compare according to search mode
11767     int r, f;
11768     switch(appData.searchMode)
11769     {
11770       case 1: // exact position match
11771         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11772         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11773             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11774         }
11775         break;
11776       case 2: // can have extra material on empty squares
11777         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11778             if(board[r][f] == EmptySquare) continue;
11779             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11780         }
11781         break;
11782       case 3: // material with exact Pawn structure
11783         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11784             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11785             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11786         } // fall through to material comparison
11787       case 4: // exact material
11788         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11789         break;
11790       case 6: // material range with given imbalance
11791         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11792         // fall through to range comparison
11793       case 5: // material range
11794         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11795     }
11796     return TRUE;
11797 }
11798
11799 int
11800 QuickScan (Board board, Move *move)
11801 {   // reconstruct game,and compare all positions in it
11802     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11803     do {
11804         int piece = move->piece;
11805         int to = move->to, from = pieceList[piece];
11806         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11807           if(!piece) return -1;
11808           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11809             piece = (++move)->piece;
11810             from = pieceList[piece];
11811             counts[pieceType[piece]]--;
11812             pieceType[piece] = (ChessSquare) move->to;
11813             counts[move->to]++;
11814           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11815             counts[pieceType[quickBoard[to]]]--;
11816             quickBoard[to] = 0; total--;
11817             move++;
11818             continue;
11819           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11820             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11821             from  = pieceList[piece]; // so this must be King
11822             quickBoard[from] = 0;
11823             pieceList[piece] = to;
11824             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11825             quickBoard[from] = 0; // rook
11826             quickBoard[to] = piece;
11827             to = move->to; piece = move->piece;
11828             goto aftercastle;
11829           }
11830         }
11831         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11832         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11833         quickBoard[from] = 0;
11834       aftercastle:
11835         quickBoard[to] = piece;
11836         pieceList[piece] = to;
11837         cnt++; turn ^= 3;
11838         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11839            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11840            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11841                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11842           ) {
11843             static int lastCounts[EmptySquare+1];
11844             int i;
11845             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11846             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11847         } else stretch = 0;
11848         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11849         move++;
11850     } while(1);
11851 }
11852
11853 void
11854 InitSearch ()
11855 {
11856     int r, f;
11857     flipSearch = FALSE;
11858     CopyBoard(soughtBoard, boards[currentMove]);
11859     soughtTotal = MakePieceList(soughtBoard, maxSought);
11860     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11861     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11862     CopyBoard(reverseBoard, boards[currentMove]);
11863     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11864         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11865         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11866         reverseBoard[r][f] = piece;
11867     }
11868     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11869     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11870     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11871                  || (boards[currentMove][CASTLING][2] == NoRights ||
11872                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11873                  && (boards[currentMove][CASTLING][5] == NoRights ||
11874                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11875       ) {
11876         flipSearch = TRUE;
11877         CopyBoard(flipBoard, soughtBoard);
11878         CopyBoard(rotateBoard, reverseBoard);
11879         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11880             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11881             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11882         }
11883     }
11884     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11885     if(appData.searchMode >= 5) {
11886         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11887         MakePieceList(soughtBoard, minSought);
11888         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11889     }
11890     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11891         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11892 }
11893
11894 GameInfo dummyInfo;
11895 static int creatingBook;
11896
11897 int
11898 GameContainsPosition (FILE *f, ListGame *lg)
11899 {
11900     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11901     int fromX, fromY, toX, toY;
11902     char promoChar;
11903     static int initDone=FALSE;
11904
11905     // weed out games based on numerical tag comparison
11906     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11907     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11908     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11909     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11910     if(!initDone) {
11911         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11912         initDone = TRUE;
11913     }
11914     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11915     else CopyBoard(boards[scratch], initialPosition); // default start position
11916     if(lg->moves) {
11917         turn = btm + 1;
11918         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11919         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11920     }
11921     if(btm) plyNr++;
11922     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11923     fseek(f, lg->offset, 0);
11924     yynewfile(f);
11925     while(1) {
11926         yyboardindex = scratch;
11927         quickFlag = plyNr+1;
11928         next = Myylex();
11929         quickFlag = 0;
11930         switch(next) {
11931             case PGNTag:
11932                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11933             default:
11934                 continue;
11935
11936             case XBoardGame:
11937             case GNUChessGame:
11938                 if(plyNr) return -1; // after we have seen moves, this is for new game
11939               continue;
11940
11941             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11942             case ImpossibleMove:
11943             case WhiteWins: // game ends here with these four
11944             case BlackWins:
11945             case GameIsDrawn:
11946             case GameUnfinished:
11947                 return -1;
11948
11949             case IllegalMove:
11950                 if(appData.testLegality) return -1;
11951             case WhiteCapturesEnPassant:
11952             case BlackCapturesEnPassant:
11953             case WhitePromotion:
11954             case BlackPromotion:
11955             case WhiteNonPromotion:
11956             case BlackNonPromotion:
11957             case NormalMove:
11958             case WhiteKingSideCastle:
11959             case WhiteQueenSideCastle:
11960             case BlackKingSideCastle:
11961             case BlackQueenSideCastle:
11962             case WhiteKingSideCastleWild:
11963             case WhiteQueenSideCastleWild:
11964             case BlackKingSideCastleWild:
11965             case BlackQueenSideCastleWild:
11966             case WhiteHSideCastleFR:
11967             case WhiteASideCastleFR:
11968             case BlackHSideCastleFR:
11969             case BlackASideCastleFR:
11970                 fromX = currentMoveString[0] - AAA;
11971                 fromY = currentMoveString[1] - ONE;
11972                 toX = currentMoveString[2] - AAA;
11973                 toY = currentMoveString[3] - ONE;
11974                 promoChar = currentMoveString[4];
11975                 break;
11976             case WhiteDrop:
11977             case BlackDrop:
11978                 fromX = next == WhiteDrop ?
11979                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11980                   (int) CharToPiece(ToLower(currentMoveString[0]));
11981                 fromY = DROP_RANK;
11982                 toX = currentMoveString[2] - AAA;
11983                 toY = currentMoveString[3] - ONE;
11984                 promoChar = 0;
11985                 break;
11986         }
11987         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11988         plyNr++;
11989         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11990         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11991         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11992         if(appData.findMirror) {
11993             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11994             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11995         }
11996     }
11997 }
11998
11999 /* Load the nth game from open file f */
12000 int
12001 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12002 {
12003     ChessMove cm;
12004     char buf[MSG_SIZ];
12005     int gn = gameNumber;
12006     ListGame *lg = NULL;
12007     int numPGNTags = 0;
12008     int err, pos = -1;
12009     GameMode oldGameMode;
12010     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12011
12012     if (appData.debugMode)
12013         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12014
12015     if (gameMode == Training )
12016         SetTrainingModeOff();
12017
12018     oldGameMode = gameMode;
12019     if (gameMode != BeginningOfGame) {
12020       Reset(FALSE, TRUE);
12021     }
12022
12023     gameFileFP = f;
12024     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12025         fclose(lastLoadGameFP);
12026     }
12027
12028     if (useList) {
12029         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12030
12031         if (lg) {
12032             fseek(f, lg->offset, 0);
12033             GameListHighlight(gameNumber);
12034             pos = lg->position;
12035             gn = 1;
12036         }
12037         else {
12038             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
12039               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12040             else
12041             DisplayError(_("Game number out of range"), 0);
12042             return FALSE;
12043         }
12044     } else {
12045         GameListDestroy();
12046         if (fseek(f, 0, 0) == -1) {
12047             if (f == lastLoadGameFP ?
12048                 gameNumber == lastLoadGameNumber + 1 :
12049                 gameNumber == 1) {
12050                 gn = 1;
12051             } else {
12052                 DisplayError(_("Can't seek on game file"), 0);
12053                 return FALSE;
12054             }
12055         }
12056     }
12057     lastLoadGameFP = f;
12058     lastLoadGameNumber = gameNumber;
12059     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12060     lastLoadGameUseList = useList;
12061
12062     yynewfile(f);
12063
12064     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12065       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12066                 lg->gameInfo.black);
12067             DisplayTitle(buf);
12068     } else if (*title != NULLCHAR) {
12069         if (gameNumber > 1) {
12070           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12071             DisplayTitle(buf);
12072         } else {
12073             DisplayTitle(title);
12074         }
12075     }
12076
12077     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12078         gameMode = PlayFromGameFile;
12079         ModeHighlight();
12080     }
12081
12082     currentMove = forwardMostMove = backwardMostMove = 0;
12083     CopyBoard(boards[0], initialPosition);
12084     StopClocks();
12085
12086     /*
12087      * Skip the first gn-1 games in the file.
12088      * Also skip over anything that precedes an identifiable
12089      * start of game marker, to avoid being confused by
12090      * garbage at the start of the file.  Currently
12091      * recognized start of game markers are the move number "1",
12092      * the pattern "gnuchess .* game", the pattern
12093      * "^[#;%] [^ ]* game file", and a PGN tag block.
12094      * A game that starts with one of the latter two patterns
12095      * will also have a move number 1, possibly
12096      * following a position diagram.
12097      * 5-4-02: Let's try being more lenient and allowing a game to
12098      * start with an unnumbered move.  Does that break anything?
12099      */
12100     cm = lastLoadGameStart = EndOfFile;
12101     while (gn > 0) {
12102         yyboardindex = forwardMostMove;
12103         cm = (ChessMove) Myylex();
12104         switch (cm) {
12105           case EndOfFile:
12106             if (cmailMsgLoaded) {
12107                 nCmailGames = CMAIL_MAX_GAMES - gn;
12108             } else {
12109                 Reset(TRUE, TRUE);
12110                 DisplayError(_("Game not found in file"), 0);
12111             }
12112             return FALSE;
12113
12114           case GNUChessGame:
12115           case XBoardGame:
12116             gn--;
12117             lastLoadGameStart = cm;
12118             break;
12119
12120           case MoveNumberOne:
12121             switch (lastLoadGameStart) {
12122               case GNUChessGame:
12123               case XBoardGame:
12124               case PGNTag:
12125                 break;
12126               case MoveNumberOne:
12127               case EndOfFile:
12128                 gn--;           /* count this game */
12129                 lastLoadGameStart = cm;
12130                 break;
12131               default:
12132                 /* impossible */
12133                 break;
12134             }
12135             break;
12136
12137           case PGNTag:
12138             switch (lastLoadGameStart) {
12139               case GNUChessGame:
12140               case PGNTag:
12141               case MoveNumberOne:
12142               case EndOfFile:
12143                 gn--;           /* count this game */
12144                 lastLoadGameStart = cm;
12145                 break;
12146               case XBoardGame:
12147                 lastLoadGameStart = cm; /* game counted already */
12148                 break;
12149               default:
12150                 /* impossible */
12151                 break;
12152             }
12153             if (gn > 0) {
12154                 do {
12155                     yyboardindex = forwardMostMove;
12156                     cm = (ChessMove) Myylex();
12157                 } while (cm == PGNTag || cm == Comment);
12158             }
12159             break;
12160
12161           case WhiteWins:
12162           case BlackWins:
12163           case GameIsDrawn:
12164             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12165                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12166                     != CMAIL_OLD_RESULT) {
12167                     nCmailResults ++ ;
12168                     cmailResult[  CMAIL_MAX_GAMES
12169                                 - gn - 1] = CMAIL_OLD_RESULT;
12170                 }
12171             }
12172             break;
12173
12174           case NormalMove:
12175             /* Only a NormalMove can be at the start of a game
12176              * without a position diagram. */
12177             if (lastLoadGameStart == EndOfFile ) {
12178               gn--;
12179               lastLoadGameStart = MoveNumberOne;
12180             }
12181             break;
12182
12183           default:
12184             break;
12185         }
12186     }
12187
12188     if (appData.debugMode)
12189       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12190
12191     if (cm == XBoardGame) {
12192         /* Skip any header junk before position diagram and/or move 1 */
12193         for (;;) {
12194             yyboardindex = forwardMostMove;
12195             cm = (ChessMove) Myylex();
12196
12197             if (cm == EndOfFile ||
12198                 cm == GNUChessGame || cm == XBoardGame) {
12199                 /* Empty game; pretend end-of-file and handle later */
12200                 cm = EndOfFile;
12201                 break;
12202             }
12203
12204             if (cm == MoveNumberOne || cm == PositionDiagram ||
12205                 cm == PGNTag || cm == Comment)
12206               break;
12207         }
12208     } else if (cm == GNUChessGame) {
12209         if (gameInfo.event != NULL) {
12210             free(gameInfo.event);
12211         }
12212         gameInfo.event = StrSave(yy_text);
12213     }
12214
12215     startedFromSetupPosition = FALSE;
12216     while (cm == PGNTag) {
12217         if (appData.debugMode)
12218           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12219         err = ParsePGNTag(yy_text, &gameInfo);
12220         if (!err) numPGNTags++;
12221
12222         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12223         if(gameInfo.variant != oldVariant) {
12224             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12225             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12226             InitPosition(TRUE);
12227             oldVariant = gameInfo.variant;
12228             if (appData.debugMode)
12229               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12230         }
12231
12232
12233         if (gameInfo.fen != NULL) {
12234           Board initial_position;
12235           startedFromSetupPosition = TRUE;
12236           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12237             Reset(TRUE, TRUE);
12238             DisplayError(_("Bad FEN position in file"), 0);
12239             return FALSE;
12240           }
12241           CopyBoard(boards[0], initial_position);
12242           if (blackPlaysFirst) {
12243             currentMove = forwardMostMove = backwardMostMove = 1;
12244             CopyBoard(boards[1], initial_position);
12245             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12246             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12247             timeRemaining[0][1] = whiteTimeRemaining;
12248             timeRemaining[1][1] = blackTimeRemaining;
12249             if (commentList[0] != NULL) {
12250               commentList[1] = commentList[0];
12251               commentList[0] = NULL;
12252             }
12253           } else {
12254             currentMove = forwardMostMove = backwardMostMove = 0;
12255           }
12256           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12257           {   int i;
12258               initialRulePlies = FENrulePlies;
12259               for( i=0; i< nrCastlingRights; i++ )
12260                   initialRights[i] = initial_position[CASTLING][i];
12261           }
12262           yyboardindex = forwardMostMove;
12263           free(gameInfo.fen);
12264           gameInfo.fen = NULL;
12265         }
12266
12267         yyboardindex = forwardMostMove;
12268         cm = (ChessMove) Myylex();
12269
12270         /* Handle comments interspersed among the tags */
12271         while (cm == Comment) {
12272             char *p;
12273             if (appData.debugMode)
12274               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12275             p = yy_text;
12276             AppendComment(currentMove, p, FALSE);
12277             yyboardindex = forwardMostMove;
12278             cm = (ChessMove) Myylex();
12279         }
12280     }
12281
12282     /* don't rely on existence of Event tag since if game was
12283      * pasted from clipboard the Event tag may not exist
12284      */
12285     if (numPGNTags > 0){
12286         char *tags;
12287         if (gameInfo.variant == VariantNormal) {
12288           VariantClass v = StringToVariant(gameInfo.event);
12289           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12290           if(v < VariantShogi) gameInfo.variant = v;
12291         }
12292         if (!matchMode) {
12293           if( appData.autoDisplayTags ) {
12294             tags = PGNTags(&gameInfo);
12295             TagsPopUp(tags, CmailMsg());
12296             free(tags);
12297           }
12298         }
12299     } else {
12300         /* Make something up, but don't display it now */
12301         SetGameInfo();
12302         TagsPopDown();
12303     }
12304
12305     if (cm == PositionDiagram) {
12306         int i, j;
12307         char *p;
12308         Board initial_position;
12309
12310         if (appData.debugMode)
12311           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12312
12313         if (!startedFromSetupPosition) {
12314             p = yy_text;
12315             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12316               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12317                 switch (*p) {
12318                   case '{':
12319                   case '[':
12320                   case '-':
12321                   case ' ':
12322                   case '\t':
12323                   case '\n':
12324                   case '\r':
12325                     break;
12326                   default:
12327                     initial_position[i][j++] = CharToPiece(*p);
12328                     break;
12329                 }
12330             while (*p == ' ' || *p == '\t' ||
12331                    *p == '\n' || *p == '\r') p++;
12332
12333             if (strncmp(p, "black", strlen("black"))==0)
12334               blackPlaysFirst = TRUE;
12335             else
12336               blackPlaysFirst = FALSE;
12337             startedFromSetupPosition = TRUE;
12338
12339             CopyBoard(boards[0], initial_position);
12340             if (blackPlaysFirst) {
12341                 currentMove = forwardMostMove = backwardMostMove = 1;
12342                 CopyBoard(boards[1], initial_position);
12343                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12344                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12345                 timeRemaining[0][1] = whiteTimeRemaining;
12346                 timeRemaining[1][1] = blackTimeRemaining;
12347                 if (commentList[0] != NULL) {
12348                     commentList[1] = commentList[0];
12349                     commentList[0] = NULL;
12350                 }
12351             } else {
12352                 currentMove = forwardMostMove = backwardMostMove = 0;
12353             }
12354         }
12355         yyboardindex = forwardMostMove;
12356         cm = (ChessMove) Myylex();
12357     }
12358
12359   if(!creatingBook) {
12360     if (first.pr == NoProc) {
12361         StartChessProgram(&first);
12362     }
12363     InitChessProgram(&first, FALSE);
12364     SendToProgram("force\n", &first);
12365     if (startedFromSetupPosition) {
12366         SendBoard(&first, forwardMostMove);
12367     if (appData.debugMode) {
12368         fprintf(debugFP, "Load Game\n");
12369     }
12370         DisplayBothClocks();
12371     }
12372   }
12373
12374     /* [HGM] server: flag to write setup moves in broadcast file as one */
12375     loadFlag = appData.suppressLoadMoves;
12376
12377     while (cm == Comment) {
12378         char *p;
12379         if (appData.debugMode)
12380           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12381         p = yy_text;
12382         AppendComment(currentMove, p, FALSE);
12383         yyboardindex = forwardMostMove;
12384         cm = (ChessMove) Myylex();
12385     }
12386
12387     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12388         cm == WhiteWins || cm == BlackWins ||
12389         cm == GameIsDrawn || cm == GameUnfinished) {
12390         DisplayMessage("", _("No moves in game"));
12391         if (cmailMsgLoaded) {
12392             if (appData.debugMode)
12393               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12394             ClearHighlights();
12395             flipView = FALSE;
12396         }
12397         DrawPosition(FALSE, boards[currentMove]);
12398         DisplayBothClocks();
12399         gameMode = EditGame;
12400         ModeHighlight();
12401         gameFileFP = NULL;
12402         cmailOldMove = 0;
12403         return TRUE;
12404     }
12405
12406     // [HGM] PV info: routine tests if comment empty
12407     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12408         DisplayComment(currentMove - 1, commentList[currentMove]);
12409     }
12410     if (!matchMode && appData.timeDelay != 0)
12411       DrawPosition(FALSE, boards[currentMove]);
12412
12413     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12414       programStats.ok_to_send = 1;
12415     }
12416
12417     /* if the first token after the PGN tags is a move
12418      * and not move number 1, retrieve it from the parser
12419      */
12420     if (cm != MoveNumberOne)
12421         LoadGameOneMove(cm);
12422
12423     /* load the remaining moves from the file */
12424     while (LoadGameOneMove(EndOfFile)) {
12425       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12426       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12427     }
12428
12429     /* rewind to the start of the game */
12430     currentMove = backwardMostMove;
12431
12432     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12433
12434     if (oldGameMode == AnalyzeFile ||
12435         oldGameMode == AnalyzeMode) {
12436       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12437       AnalyzeFileEvent();
12438     }
12439
12440     if(creatingBook) return TRUE;
12441     if (!matchMode && pos > 0) {
12442         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12443     } else
12444     if (matchMode || appData.timeDelay == 0) {
12445       ToEndEvent();
12446     } else if (appData.timeDelay > 0) {
12447       AutoPlayGameLoop();
12448     }
12449
12450     if (appData.debugMode)
12451         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12452
12453     loadFlag = 0; /* [HGM] true game starts */
12454     return TRUE;
12455 }
12456
12457 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12458 int
12459 ReloadPosition (int offset)
12460 {
12461     int positionNumber = lastLoadPositionNumber + offset;
12462     if (lastLoadPositionFP == NULL) {
12463         DisplayError(_("No position has been loaded yet"), 0);
12464         return FALSE;
12465     }
12466     if (positionNumber <= 0) {
12467         DisplayError(_("Can't back up any further"), 0);
12468         return FALSE;
12469     }
12470     return LoadPosition(lastLoadPositionFP, positionNumber,
12471                         lastLoadPositionTitle);
12472 }
12473
12474 /* Load the nth position from the given file */
12475 int
12476 LoadPositionFromFile (char *filename, int n, char *title)
12477 {
12478     FILE *f;
12479     char buf[MSG_SIZ];
12480
12481     if (strcmp(filename, "-") == 0) {
12482         return LoadPosition(stdin, n, "stdin");
12483     } else {
12484         f = fopen(filename, "rb");
12485         if (f == NULL) {
12486             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12487             DisplayError(buf, errno);
12488             return FALSE;
12489         } else {
12490             return LoadPosition(f, n, title);
12491         }
12492     }
12493 }
12494
12495 /* Load the nth position from the given open file, and close it */
12496 int
12497 LoadPosition (FILE *f, int positionNumber, char *title)
12498 {
12499     char *p, line[MSG_SIZ];
12500     Board initial_position;
12501     int i, j, fenMode, pn;
12502
12503     if (gameMode == Training )
12504         SetTrainingModeOff();
12505
12506     if (gameMode != BeginningOfGame) {
12507         Reset(FALSE, TRUE);
12508     }
12509     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12510         fclose(lastLoadPositionFP);
12511     }
12512     if (positionNumber == 0) positionNumber = 1;
12513     lastLoadPositionFP = f;
12514     lastLoadPositionNumber = positionNumber;
12515     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12516     if (first.pr == NoProc && !appData.noChessProgram) {
12517       StartChessProgram(&first);
12518       InitChessProgram(&first, FALSE);
12519     }
12520     pn = positionNumber;
12521     if (positionNumber < 0) {
12522         /* Negative position number means to seek to that byte offset */
12523         if (fseek(f, -positionNumber, 0) == -1) {
12524             DisplayError(_("Can't seek on position file"), 0);
12525             return FALSE;
12526         };
12527         pn = 1;
12528     } else {
12529         if (fseek(f, 0, 0) == -1) {
12530             if (f == lastLoadPositionFP ?
12531                 positionNumber == lastLoadPositionNumber + 1 :
12532                 positionNumber == 1) {
12533                 pn = 1;
12534             } else {
12535                 DisplayError(_("Can't seek on position file"), 0);
12536                 return FALSE;
12537             }
12538         }
12539     }
12540     /* See if this file is FEN or old-style xboard */
12541     if (fgets(line, MSG_SIZ, f) == NULL) {
12542         DisplayError(_("Position not found in file"), 0);
12543         return FALSE;
12544     }
12545     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12546     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12547
12548     if (pn >= 2) {
12549         if (fenMode || line[0] == '#') pn--;
12550         while (pn > 0) {
12551             /* skip positions before number pn */
12552             if (fgets(line, MSG_SIZ, f) == NULL) {
12553                 Reset(TRUE, TRUE);
12554                 DisplayError(_("Position not found in file"), 0);
12555                 return FALSE;
12556             }
12557             if (fenMode || line[0] == '#') pn--;
12558         }
12559     }
12560
12561     if (fenMode) {
12562         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12563             DisplayError(_("Bad FEN position in file"), 0);
12564             return FALSE;
12565         }
12566     } else {
12567         (void) fgets(line, MSG_SIZ, f);
12568         (void) fgets(line, MSG_SIZ, f);
12569
12570         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12571             (void) fgets(line, MSG_SIZ, f);
12572             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12573                 if (*p == ' ')
12574                   continue;
12575                 initial_position[i][j++] = CharToPiece(*p);
12576             }
12577         }
12578
12579         blackPlaysFirst = FALSE;
12580         if (!feof(f)) {
12581             (void) fgets(line, MSG_SIZ, f);
12582             if (strncmp(line, "black", strlen("black"))==0)
12583               blackPlaysFirst = TRUE;
12584         }
12585     }
12586     startedFromSetupPosition = TRUE;
12587
12588     CopyBoard(boards[0], initial_position);
12589     if (blackPlaysFirst) {
12590         currentMove = forwardMostMove = backwardMostMove = 1;
12591         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12592         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12593         CopyBoard(boards[1], initial_position);
12594         DisplayMessage("", _("Black to play"));
12595     } else {
12596         currentMove = forwardMostMove = backwardMostMove = 0;
12597         DisplayMessage("", _("White to play"));
12598     }
12599     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12600     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12601         SendToProgram("force\n", &first);
12602         SendBoard(&first, forwardMostMove);
12603     }
12604     if (appData.debugMode) {
12605 int i, j;
12606   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12607   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12608         fprintf(debugFP, "Load Position\n");
12609     }
12610
12611     if (positionNumber > 1) {
12612       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12613         DisplayTitle(line);
12614     } else {
12615         DisplayTitle(title);
12616     }
12617     gameMode = EditGame;
12618     ModeHighlight();
12619     ResetClocks();
12620     timeRemaining[0][1] = whiteTimeRemaining;
12621     timeRemaining[1][1] = blackTimeRemaining;
12622     DrawPosition(FALSE, boards[currentMove]);
12623
12624     return TRUE;
12625 }
12626
12627
12628 void
12629 CopyPlayerNameIntoFileName (char **dest, char *src)
12630 {
12631     while (*src != NULLCHAR && *src != ',') {
12632         if (*src == ' ') {
12633             *(*dest)++ = '_';
12634             src++;
12635         } else {
12636             *(*dest)++ = *src++;
12637         }
12638     }
12639 }
12640
12641 char *
12642 DefaultFileName (char *ext)
12643 {
12644     static char def[MSG_SIZ];
12645     char *p;
12646
12647     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12648         p = def;
12649         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12650         *p++ = '-';
12651         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12652         *p++ = '.';
12653         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12654     } else {
12655         def[0] = NULLCHAR;
12656     }
12657     return def;
12658 }
12659
12660 /* Save the current game to the given file */
12661 int
12662 SaveGameToFile (char *filename, int append)
12663 {
12664     FILE *f;
12665     char buf[MSG_SIZ];
12666     int result, i, t,tot=0;
12667
12668     if (strcmp(filename, "-") == 0) {
12669         return SaveGame(stdout, 0, NULL);
12670     } else {
12671         for(i=0; i<10; i++) { // upto 10 tries
12672              f = fopen(filename, append ? "a" : "w");
12673              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12674              if(f || errno != 13) break;
12675              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12676              tot += t;
12677         }
12678         if (f == NULL) {
12679             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12680             DisplayError(buf, errno);
12681             return FALSE;
12682         } else {
12683             safeStrCpy(buf, lastMsg, MSG_SIZ);
12684             DisplayMessage(_("Waiting for access to save file"), "");
12685             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12686             DisplayMessage(_("Saving game"), "");
12687             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12688             result = SaveGame(f, 0, NULL);
12689             DisplayMessage(buf, "");
12690             return result;
12691         }
12692     }
12693 }
12694
12695 char *
12696 SavePart (char *str)
12697 {
12698     static char buf[MSG_SIZ];
12699     char *p;
12700
12701     p = strchr(str, ' ');
12702     if (p == NULL) return str;
12703     strncpy(buf, str, p - str);
12704     buf[p - str] = NULLCHAR;
12705     return buf;
12706 }
12707
12708 #define PGN_MAX_LINE 75
12709
12710 #define PGN_SIDE_WHITE  0
12711 #define PGN_SIDE_BLACK  1
12712
12713 static int
12714 FindFirstMoveOutOfBook (int side)
12715 {
12716     int result = -1;
12717
12718     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12719         int index = backwardMostMove;
12720         int has_book_hit = 0;
12721
12722         if( (index % 2) != side ) {
12723             index++;
12724         }
12725
12726         while( index < forwardMostMove ) {
12727             /* Check to see if engine is in book */
12728             int depth = pvInfoList[index].depth;
12729             int score = pvInfoList[index].score;
12730             int in_book = 0;
12731
12732             if( depth <= 2 ) {
12733                 in_book = 1;
12734             }
12735             else if( score == 0 && depth == 63 ) {
12736                 in_book = 1; /* Zappa */
12737             }
12738             else if( score == 2 && depth == 99 ) {
12739                 in_book = 1; /* Abrok */
12740             }
12741
12742             has_book_hit += in_book;
12743
12744             if( ! in_book ) {
12745                 result = index;
12746
12747                 break;
12748             }
12749
12750             index += 2;
12751         }
12752     }
12753
12754     return result;
12755 }
12756
12757 void
12758 GetOutOfBookInfo (char * buf)
12759 {
12760     int oob[2];
12761     int i;
12762     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12763
12764     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12765     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12766
12767     *buf = '\0';
12768
12769     if( oob[0] >= 0 || oob[1] >= 0 ) {
12770         for( i=0; i<2; i++ ) {
12771             int idx = oob[i];
12772
12773             if( idx >= 0 ) {
12774                 if( i > 0 && oob[0] >= 0 ) {
12775                     strcat( buf, "   " );
12776                 }
12777
12778                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12779                 sprintf( buf+strlen(buf), "%s%.2f",
12780                     pvInfoList[idx].score >= 0 ? "+" : "",
12781                     pvInfoList[idx].score / 100.0 );
12782             }
12783         }
12784     }
12785 }
12786
12787 /* Save game in PGN style and close the file */
12788 int
12789 SaveGamePGN (FILE *f)
12790 {
12791     int i, offset, linelen, newblock;
12792 //    char *movetext;
12793     char numtext[32];
12794     int movelen, numlen, blank;
12795     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12796
12797     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12798
12799     PrintPGNTags(f, &gameInfo);
12800
12801     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12802
12803     if (backwardMostMove > 0 || startedFromSetupPosition) {
12804         char *fen = PositionToFEN(backwardMostMove, NULL);
12805         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12806         fprintf(f, "\n{--------------\n");
12807         PrintPosition(f, backwardMostMove);
12808         fprintf(f, "--------------}\n");
12809         free(fen);
12810     }
12811     else {
12812         /* [AS] Out of book annotation */
12813         if( appData.saveOutOfBookInfo ) {
12814             char buf[64];
12815
12816             GetOutOfBookInfo( buf );
12817
12818             if( buf[0] != '\0' ) {
12819                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12820             }
12821         }
12822
12823         fprintf(f, "\n");
12824     }
12825
12826     i = backwardMostMove;
12827     linelen = 0;
12828     newblock = TRUE;
12829
12830     while (i < forwardMostMove) {
12831         /* Print comments preceding this move */
12832         if (commentList[i] != NULL) {
12833             if (linelen > 0) fprintf(f, "\n");
12834             fprintf(f, "%s", commentList[i]);
12835             linelen = 0;
12836             newblock = TRUE;
12837         }
12838
12839         /* Format move number */
12840         if ((i % 2) == 0)
12841           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12842         else
12843           if (newblock)
12844             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12845           else
12846             numtext[0] = NULLCHAR;
12847
12848         numlen = strlen(numtext);
12849         newblock = FALSE;
12850
12851         /* Print move number */
12852         blank = linelen > 0 && numlen > 0;
12853         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12854             fprintf(f, "\n");
12855             linelen = 0;
12856             blank = 0;
12857         }
12858         if (blank) {
12859             fprintf(f, " ");
12860             linelen++;
12861         }
12862         fprintf(f, "%s", numtext);
12863         linelen += numlen;
12864
12865         /* Get move */
12866         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12867         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12868
12869         /* Print move */
12870         blank = linelen > 0 && movelen > 0;
12871         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12872             fprintf(f, "\n");
12873             linelen = 0;
12874             blank = 0;
12875         }
12876         if (blank) {
12877             fprintf(f, " ");
12878             linelen++;
12879         }
12880         fprintf(f, "%s", move_buffer);
12881         linelen += movelen;
12882
12883         /* [AS] Add PV info if present */
12884         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12885             /* [HGM] add time */
12886             char buf[MSG_SIZ]; int seconds;
12887
12888             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12889
12890             if( seconds <= 0)
12891               buf[0] = 0;
12892             else
12893               if( seconds < 30 )
12894                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12895               else
12896                 {
12897                   seconds = (seconds + 4)/10; // round to full seconds
12898                   if( seconds < 60 )
12899                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12900                   else
12901                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12902                 }
12903
12904             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12905                       pvInfoList[i].score >= 0 ? "+" : "",
12906                       pvInfoList[i].score / 100.0,
12907                       pvInfoList[i].depth,
12908                       buf );
12909
12910             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12911
12912             /* Print score/depth */
12913             blank = linelen > 0 && movelen > 0;
12914             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12915                 fprintf(f, "\n");
12916                 linelen = 0;
12917                 blank = 0;
12918             }
12919             if (blank) {
12920                 fprintf(f, " ");
12921                 linelen++;
12922             }
12923             fprintf(f, "%s", move_buffer);
12924             linelen += movelen;
12925         }
12926
12927         i++;
12928     }
12929
12930     /* Start a new line */
12931     if (linelen > 0) fprintf(f, "\n");
12932
12933     /* Print comments after last move */
12934     if (commentList[i] != NULL) {
12935         fprintf(f, "%s\n", commentList[i]);
12936     }
12937
12938     /* Print result */
12939     if (gameInfo.resultDetails != NULL &&
12940         gameInfo.resultDetails[0] != NULLCHAR) {
12941         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12942                 PGNResult(gameInfo.result));
12943     } else {
12944         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12945     }
12946
12947     fclose(f);
12948     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12949     return TRUE;
12950 }
12951
12952 /* Save game in old style and close the file */
12953 int
12954 SaveGameOldStyle (FILE *f)
12955 {
12956     int i, offset;
12957     time_t tm;
12958
12959     tm = time((time_t *) NULL);
12960
12961     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12962     PrintOpponents(f);
12963
12964     if (backwardMostMove > 0 || startedFromSetupPosition) {
12965         fprintf(f, "\n[--------------\n");
12966         PrintPosition(f, backwardMostMove);
12967         fprintf(f, "--------------]\n");
12968     } else {
12969         fprintf(f, "\n");
12970     }
12971
12972     i = backwardMostMove;
12973     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12974
12975     while (i < forwardMostMove) {
12976         if (commentList[i] != NULL) {
12977             fprintf(f, "[%s]\n", commentList[i]);
12978         }
12979
12980         if ((i % 2) == 1) {
12981             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12982             i++;
12983         } else {
12984             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12985             i++;
12986             if (commentList[i] != NULL) {
12987                 fprintf(f, "\n");
12988                 continue;
12989             }
12990             if (i >= forwardMostMove) {
12991                 fprintf(f, "\n");
12992                 break;
12993             }
12994             fprintf(f, "%s\n", parseList[i]);
12995             i++;
12996         }
12997     }
12998
12999     if (commentList[i] != NULL) {
13000         fprintf(f, "[%s]\n", commentList[i]);
13001     }
13002
13003     /* This isn't really the old style, but it's close enough */
13004     if (gameInfo.resultDetails != NULL &&
13005         gameInfo.resultDetails[0] != NULLCHAR) {
13006         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13007                 gameInfo.resultDetails);
13008     } else {
13009         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13010     }
13011
13012     fclose(f);
13013     return TRUE;
13014 }
13015
13016 /* Save the current game to open file f and close the file */
13017 int
13018 SaveGame (FILE *f, int dummy, char *dummy2)
13019 {
13020     if (gameMode == EditPosition) EditPositionDone(TRUE);
13021     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13022     if (appData.oldSaveStyle)
13023       return SaveGameOldStyle(f);
13024     else
13025       return SaveGamePGN(f);
13026 }
13027
13028 /* Save the current position to the given file */
13029 int
13030 SavePositionToFile (char *filename)
13031 {
13032     FILE *f;
13033     char buf[MSG_SIZ];
13034
13035     if (strcmp(filename, "-") == 0) {
13036         return SavePosition(stdout, 0, NULL);
13037     } else {
13038         f = fopen(filename, "a");
13039         if (f == NULL) {
13040             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13041             DisplayError(buf, errno);
13042             return FALSE;
13043         } else {
13044             safeStrCpy(buf, lastMsg, MSG_SIZ);
13045             DisplayMessage(_("Waiting for access to save file"), "");
13046             flock(fileno(f), LOCK_EX); // [HGM] lock
13047             DisplayMessage(_("Saving position"), "");
13048             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13049             SavePosition(f, 0, NULL);
13050             DisplayMessage(buf, "");
13051             return TRUE;
13052         }
13053     }
13054 }
13055
13056 /* Save the current position to the given open file and close the file */
13057 int
13058 SavePosition (FILE *f, int dummy, char *dummy2)
13059 {
13060     time_t tm;
13061     char *fen;
13062
13063     if (gameMode == EditPosition) EditPositionDone(TRUE);
13064     if (appData.oldSaveStyle) {
13065         tm = time((time_t *) NULL);
13066
13067         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13068         PrintOpponents(f);
13069         fprintf(f, "[--------------\n");
13070         PrintPosition(f, currentMove);
13071         fprintf(f, "--------------]\n");
13072     } else {
13073         fen = PositionToFEN(currentMove, NULL);
13074         fprintf(f, "%s\n", fen);
13075         free(fen);
13076     }
13077     fclose(f);
13078     return TRUE;
13079 }
13080
13081 void
13082 ReloadCmailMsgEvent (int unregister)
13083 {
13084 #if !WIN32
13085     static char *inFilename = NULL;
13086     static char *outFilename;
13087     int i;
13088     struct stat inbuf, outbuf;
13089     int status;
13090
13091     /* Any registered moves are unregistered if unregister is set, */
13092     /* i.e. invoked by the signal handler */
13093     if (unregister) {
13094         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13095             cmailMoveRegistered[i] = FALSE;
13096             if (cmailCommentList[i] != NULL) {
13097                 free(cmailCommentList[i]);
13098                 cmailCommentList[i] = NULL;
13099             }
13100         }
13101         nCmailMovesRegistered = 0;
13102     }
13103
13104     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13105         cmailResult[i] = CMAIL_NOT_RESULT;
13106     }
13107     nCmailResults = 0;
13108
13109     if (inFilename == NULL) {
13110         /* Because the filenames are static they only get malloced once  */
13111         /* and they never get freed                                      */
13112         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13113         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13114
13115         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13116         sprintf(outFilename, "%s.out", appData.cmailGameName);
13117     }
13118
13119     status = stat(outFilename, &outbuf);
13120     if (status < 0) {
13121         cmailMailedMove = FALSE;
13122     } else {
13123         status = stat(inFilename, &inbuf);
13124         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13125     }
13126
13127     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13128        counts the games, notes how each one terminated, etc.
13129
13130        It would be nice to remove this kludge and instead gather all
13131        the information while building the game list.  (And to keep it
13132        in the game list nodes instead of having a bunch of fixed-size
13133        parallel arrays.)  Note this will require getting each game's
13134        termination from the PGN tags, as the game list builder does
13135        not process the game moves.  --mann
13136        */
13137     cmailMsgLoaded = TRUE;
13138     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13139
13140     /* Load first game in the file or popup game menu */
13141     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13142
13143 #endif /* !WIN32 */
13144     return;
13145 }
13146
13147 int
13148 RegisterMove ()
13149 {
13150     FILE *f;
13151     char string[MSG_SIZ];
13152
13153     if (   cmailMailedMove
13154         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13155         return TRUE;            /* Allow free viewing  */
13156     }
13157
13158     /* Unregister move to ensure that we don't leave RegisterMove        */
13159     /* with the move registered when the conditions for registering no   */
13160     /* longer hold                                                       */
13161     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13162         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13163         nCmailMovesRegistered --;
13164
13165         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13166           {
13167               free(cmailCommentList[lastLoadGameNumber - 1]);
13168               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13169           }
13170     }
13171
13172     if (cmailOldMove == -1) {
13173         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13174         return FALSE;
13175     }
13176
13177     if (currentMove > cmailOldMove + 1) {
13178         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13179         return FALSE;
13180     }
13181
13182     if (currentMove < cmailOldMove) {
13183         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13184         return FALSE;
13185     }
13186
13187     if (forwardMostMove > currentMove) {
13188         /* Silently truncate extra moves */
13189         TruncateGame();
13190     }
13191
13192     if (   (currentMove == cmailOldMove + 1)
13193         || (   (currentMove == cmailOldMove)
13194             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13195                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13196         if (gameInfo.result != GameUnfinished) {
13197             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13198         }
13199
13200         if (commentList[currentMove] != NULL) {
13201             cmailCommentList[lastLoadGameNumber - 1]
13202               = StrSave(commentList[currentMove]);
13203         }
13204         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13205
13206         if (appData.debugMode)
13207           fprintf(debugFP, "Saving %s for game %d\n",
13208                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13209
13210         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13211
13212         f = fopen(string, "w");
13213         if (appData.oldSaveStyle) {
13214             SaveGameOldStyle(f); /* also closes the file */
13215
13216             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13217             f = fopen(string, "w");
13218             SavePosition(f, 0, NULL); /* also closes the file */
13219         } else {
13220             fprintf(f, "{--------------\n");
13221             PrintPosition(f, currentMove);
13222             fprintf(f, "--------------}\n\n");
13223
13224             SaveGame(f, 0, NULL); /* also closes the file*/
13225         }
13226
13227         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13228         nCmailMovesRegistered ++;
13229     } else if (nCmailGames == 1) {
13230         DisplayError(_("You have not made a move yet"), 0);
13231         return FALSE;
13232     }
13233
13234     return TRUE;
13235 }
13236
13237 void
13238 MailMoveEvent ()
13239 {
13240 #if !WIN32
13241     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13242     FILE *commandOutput;
13243     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13244     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13245     int nBuffers;
13246     int i;
13247     int archived;
13248     char *arcDir;
13249
13250     if (! cmailMsgLoaded) {
13251         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13252         return;
13253     }
13254
13255     if (nCmailGames == nCmailResults) {
13256         DisplayError(_("No unfinished games"), 0);
13257         return;
13258     }
13259
13260 #if CMAIL_PROHIBIT_REMAIL
13261     if (cmailMailedMove) {
13262       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);
13263         DisplayError(msg, 0);
13264         return;
13265     }
13266 #endif
13267
13268     if (! (cmailMailedMove || RegisterMove())) return;
13269
13270     if (   cmailMailedMove
13271         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13272       snprintf(string, MSG_SIZ, partCommandString,
13273                appData.debugMode ? " -v" : "", appData.cmailGameName);
13274         commandOutput = popen(string, "r");
13275
13276         if (commandOutput == NULL) {
13277             DisplayError(_("Failed to invoke cmail"), 0);
13278         } else {
13279             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13280                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13281             }
13282             if (nBuffers > 1) {
13283                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13284                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13285                 nBytes = MSG_SIZ - 1;
13286             } else {
13287                 (void) memcpy(msg, buffer, nBytes);
13288             }
13289             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13290
13291             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13292                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13293
13294                 archived = TRUE;
13295                 for (i = 0; i < nCmailGames; i ++) {
13296                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13297                         archived = FALSE;
13298                     }
13299                 }
13300                 if (   archived
13301                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13302                         != NULL)) {
13303                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13304                            arcDir,
13305                            appData.cmailGameName,
13306                            gameInfo.date);
13307                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13308                     cmailMsgLoaded = FALSE;
13309                 }
13310             }
13311
13312             DisplayInformation(msg);
13313             pclose(commandOutput);
13314         }
13315     } else {
13316         if ((*cmailMsg) != '\0') {
13317             DisplayInformation(cmailMsg);
13318         }
13319     }
13320
13321     return;
13322 #endif /* !WIN32 */
13323 }
13324
13325 char *
13326 CmailMsg ()
13327 {
13328 #if WIN32
13329     return NULL;
13330 #else
13331     int  prependComma = 0;
13332     char number[5];
13333     char string[MSG_SIZ];       /* Space for game-list */
13334     int  i;
13335
13336     if (!cmailMsgLoaded) return "";
13337
13338     if (cmailMailedMove) {
13339       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13340     } else {
13341         /* Create a list of games left */
13342       snprintf(string, MSG_SIZ, "[");
13343         for (i = 0; i < nCmailGames; i ++) {
13344             if (! (   cmailMoveRegistered[i]
13345                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13346                 if (prependComma) {
13347                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13348                 } else {
13349                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13350                     prependComma = 1;
13351                 }
13352
13353                 strcat(string, number);
13354             }
13355         }
13356         strcat(string, "]");
13357
13358         if (nCmailMovesRegistered + nCmailResults == 0) {
13359             switch (nCmailGames) {
13360               case 1:
13361                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13362                 break;
13363
13364               case 2:
13365                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13366                 break;
13367
13368               default:
13369                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13370                          nCmailGames);
13371                 break;
13372             }
13373         } else {
13374             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13375               case 1:
13376                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13377                          string);
13378                 break;
13379
13380               case 0:
13381                 if (nCmailResults == nCmailGames) {
13382                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13383                 } else {
13384                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13385                 }
13386                 break;
13387
13388               default:
13389                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13390                          string);
13391             }
13392         }
13393     }
13394     return cmailMsg;
13395 #endif /* WIN32 */
13396 }
13397
13398 void
13399 ResetGameEvent ()
13400 {
13401     if (gameMode == Training)
13402       SetTrainingModeOff();
13403
13404     Reset(TRUE, TRUE);
13405     cmailMsgLoaded = FALSE;
13406     if (appData.icsActive) {
13407       SendToICS(ics_prefix);
13408       SendToICS("refresh\n");
13409     }
13410 }
13411
13412 void
13413 ExitEvent (int status)
13414 {
13415     exiting++;
13416     if (exiting > 2) {
13417       /* Give up on clean exit */
13418       exit(status);
13419     }
13420     if (exiting > 1) {
13421       /* Keep trying for clean exit */
13422       return;
13423     }
13424
13425     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13426
13427     if (telnetISR != NULL) {
13428       RemoveInputSource(telnetISR);
13429     }
13430     if (icsPR != NoProc) {
13431       DestroyChildProcess(icsPR, TRUE);
13432     }
13433
13434     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13435     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13436
13437     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13438     /* make sure this other one finishes before killing it!                  */
13439     if(endingGame) { int count = 0;
13440         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13441         while(endingGame && count++ < 10) DoSleep(1);
13442         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13443     }
13444
13445     /* Kill off chess programs */
13446     if (first.pr != NoProc) {
13447         ExitAnalyzeMode();
13448
13449         DoSleep( appData.delayBeforeQuit );
13450         SendToProgram("quit\n", &first);
13451         DoSleep( appData.delayAfterQuit );
13452         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13453     }
13454     if (second.pr != NoProc) {
13455         DoSleep( appData.delayBeforeQuit );
13456         SendToProgram("quit\n", &second);
13457         DoSleep( appData.delayAfterQuit );
13458         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13459     }
13460     if (first.isr != NULL) {
13461         RemoveInputSource(first.isr);
13462     }
13463     if (second.isr != NULL) {
13464         RemoveInputSource(second.isr);
13465     }
13466
13467     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13468     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13469
13470     ShutDownFrontEnd();
13471     exit(status);
13472 }
13473
13474 void
13475 PauseEngine (ChessProgramState *cps)
13476 {
13477     SendToProgram("pause\n", cps);
13478     cps->pause = 2;
13479 }
13480
13481 void
13482 UnPauseEngine (ChessProgramState *cps)
13483 {
13484     SendToProgram("resume\n", cps);
13485     cps->pause = 1;
13486 }
13487
13488 void
13489 PauseEvent ()
13490 {
13491     if (appData.debugMode)
13492         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13493     if (pausing) {
13494         pausing = FALSE;
13495         ModeHighlight();
13496         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13497             StartClocks();
13498             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13499                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13500                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13501             }
13502             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13503             HandleMachineMove(stashedInputMove, stalledEngine);
13504             stalledEngine = NULL;
13505             return;
13506         }
13507         if (gameMode == MachinePlaysWhite ||
13508             gameMode == TwoMachinesPlay   ||
13509             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13510             if(first.pause)  UnPauseEngine(&first);
13511             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13512             if(second.pause) UnPauseEngine(&second);
13513             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13514             StartClocks();
13515         } else {
13516             DisplayBothClocks();
13517         }
13518         if (gameMode == PlayFromGameFile) {
13519             if (appData.timeDelay >= 0)
13520                 AutoPlayGameLoop();
13521         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13522             Reset(FALSE, TRUE);
13523             SendToICS(ics_prefix);
13524             SendToICS("refresh\n");
13525         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13526             ForwardInner(forwardMostMove);
13527         }
13528         pauseExamInvalid = FALSE;
13529     } else {
13530         switch (gameMode) {
13531           default:
13532             return;
13533           case IcsExamining:
13534             pauseExamForwardMostMove = forwardMostMove;
13535             pauseExamInvalid = FALSE;
13536             /* fall through */
13537           case IcsObserving:
13538           case IcsPlayingWhite:
13539           case IcsPlayingBlack:
13540             pausing = TRUE;
13541             ModeHighlight();
13542             return;
13543           case PlayFromGameFile:
13544             (void) StopLoadGameTimer();
13545             pausing = TRUE;
13546             ModeHighlight();
13547             break;
13548           case BeginningOfGame:
13549             if (appData.icsActive) return;
13550             /* else fall through */
13551           case MachinePlaysWhite:
13552           case MachinePlaysBlack:
13553           case TwoMachinesPlay:
13554             if (forwardMostMove == 0)
13555               return;           /* don't pause if no one has moved */
13556             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13557                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13558                 if(onMove->pause) {           // thinking engine can be paused
13559                     PauseEngine(onMove);      // do it
13560                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13561                         PauseEngine(onMove->other);
13562                     else
13563                         SendToProgram("easy\n", onMove->other);
13564                     StopClocks();
13565                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13566             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13567                 if(first.pause) {
13568                     PauseEngine(&first);
13569                     StopClocks();
13570                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13571             } else { // human on move, pause pondering by either method
13572                 if(first.pause)
13573                     PauseEngine(&first);
13574                 else if(appData.ponderNextMove)
13575                     SendToProgram("easy\n", &first);
13576                 StopClocks();
13577             }
13578             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13579           case AnalyzeMode:
13580             pausing = TRUE;
13581             ModeHighlight();
13582             break;
13583         }
13584     }
13585 }
13586
13587 void
13588 EditCommentEvent ()
13589 {
13590     char title[MSG_SIZ];
13591
13592     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13593       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13594     } else {
13595       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13596                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13597                parseList[currentMove - 1]);
13598     }
13599
13600     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13601 }
13602
13603
13604 void
13605 EditTagsEvent ()
13606 {
13607     char *tags = PGNTags(&gameInfo);
13608     bookUp = FALSE;
13609     EditTagsPopUp(tags, NULL);
13610     free(tags);
13611 }
13612
13613 void
13614 ToggleSecond ()
13615 {
13616   if(second.analyzing) {
13617     SendToProgram("exit\n", &second);
13618     second.analyzing = FALSE;
13619   } else {
13620     if (second.pr == NoProc) StartChessProgram(&second);
13621     InitChessProgram(&second, FALSE);
13622     FeedMovesToProgram(&second, currentMove);
13623
13624     SendToProgram("analyze\n", &second);
13625     second.analyzing = TRUE;
13626   }
13627 }
13628
13629 /* Toggle ShowThinking */
13630 void
13631 ToggleShowThinking()
13632 {
13633   appData.showThinking = !appData.showThinking;
13634   ShowThinkingEvent();
13635 }
13636
13637 int
13638 AnalyzeModeEvent ()
13639 {
13640     char buf[MSG_SIZ];
13641
13642     if (!first.analysisSupport) {
13643       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13644       DisplayError(buf, 0);
13645       return 0;
13646     }
13647     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13648     if (appData.icsActive) {
13649         if (gameMode != IcsObserving) {
13650           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13651             DisplayError(buf, 0);
13652             /* secure check */
13653             if (appData.icsEngineAnalyze) {
13654                 if (appData.debugMode)
13655                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13656                 ExitAnalyzeMode();
13657                 ModeHighlight();
13658             }
13659             return 0;
13660         }
13661         /* if enable, user wants to disable icsEngineAnalyze */
13662         if (appData.icsEngineAnalyze) {
13663                 ExitAnalyzeMode();
13664                 ModeHighlight();
13665                 return 0;
13666         }
13667         appData.icsEngineAnalyze = TRUE;
13668         if (appData.debugMode)
13669             fprintf(debugFP, "ICS engine analyze starting... \n");
13670     }
13671
13672     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13673     if (appData.noChessProgram || gameMode == AnalyzeMode)
13674       return 0;
13675
13676     if (gameMode != AnalyzeFile) {
13677         if (!appData.icsEngineAnalyze) {
13678                EditGameEvent();
13679                if (gameMode != EditGame) return 0;
13680         }
13681         if (!appData.showThinking) ToggleShowThinking();
13682         ResurrectChessProgram();
13683         SendToProgram("analyze\n", &first);
13684         first.analyzing = TRUE;
13685         /*first.maybeThinking = TRUE;*/
13686         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13687         EngineOutputPopUp();
13688     }
13689     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13690     pausing = FALSE;
13691     ModeHighlight();
13692     SetGameInfo();
13693
13694     StartAnalysisClock();
13695     GetTimeMark(&lastNodeCountTime);
13696     lastNodeCount = 0;
13697     return 1;
13698 }
13699
13700 void
13701 AnalyzeFileEvent ()
13702 {
13703     if (appData.noChessProgram || gameMode == AnalyzeFile)
13704       return;
13705
13706     if (!first.analysisSupport) {
13707       char buf[MSG_SIZ];
13708       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13709       DisplayError(buf, 0);
13710       return;
13711     }
13712
13713     if (gameMode != AnalyzeMode) {
13714         keepInfo = 1; // mere annotating should not alter PGN tags
13715         EditGameEvent();
13716         keepInfo = 0;
13717         if (gameMode != EditGame) return;
13718         if (!appData.showThinking) ToggleShowThinking();
13719         ResurrectChessProgram();
13720         SendToProgram("analyze\n", &first);
13721         first.analyzing = TRUE;
13722         /*first.maybeThinking = TRUE;*/
13723         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13724         EngineOutputPopUp();
13725     }
13726     gameMode = AnalyzeFile;
13727     pausing = FALSE;
13728     ModeHighlight();
13729
13730     StartAnalysisClock();
13731     GetTimeMark(&lastNodeCountTime);
13732     lastNodeCount = 0;
13733     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13734     AnalysisPeriodicEvent(1);
13735 }
13736
13737 void
13738 MachineWhiteEvent ()
13739 {
13740     char buf[MSG_SIZ];
13741     char *bookHit = NULL;
13742
13743     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13744       return;
13745
13746
13747     if (gameMode == PlayFromGameFile ||
13748         gameMode == TwoMachinesPlay  ||
13749         gameMode == Training         ||
13750         gameMode == AnalyzeMode      ||
13751         gameMode == EndOfGame)
13752         EditGameEvent();
13753
13754     if (gameMode == EditPosition)
13755         EditPositionDone(TRUE);
13756
13757     if (!WhiteOnMove(currentMove)) {
13758         DisplayError(_("It is not White's turn"), 0);
13759         return;
13760     }
13761
13762     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13763       ExitAnalyzeMode();
13764
13765     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13766         gameMode == AnalyzeFile)
13767         TruncateGame();
13768
13769     ResurrectChessProgram();    /* in case it isn't running */
13770     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13771         gameMode = MachinePlaysWhite;
13772         ResetClocks();
13773     } else
13774     gameMode = MachinePlaysWhite;
13775     pausing = FALSE;
13776     ModeHighlight();
13777     SetGameInfo();
13778     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13779     DisplayTitle(buf);
13780     if (first.sendName) {
13781       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13782       SendToProgram(buf, &first);
13783     }
13784     if (first.sendTime) {
13785       if (first.useColors) {
13786         SendToProgram("black\n", &first); /*gnu kludge*/
13787       }
13788       SendTimeRemaining(&first, TRUE);
13789     }
13790     if (first.useColors) {
13791       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13792     }
13793     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13794     SetMachineThinkingEnables();
13795     first.maybeThinking = TRUE;
13796     StartClocks();
13797     firstMove = FALSE;
13798
13799     if (appData.autoFlipView && !flipView) {
13800       flipView = !flipView;
13801       DrawPosition(FALSE, NULL);
13802       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13803     }
13804
13805     if(bookHit) { // [HGM] book: simulate book reply
13806         static char bookMove[MSG_SIZ]; // a bit generous?
13807
13808         programStats.nodes = programStats.depth = programStats.time =
13809         programStats.score = programStats.got_only_move = 0;
13810         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13811
13812         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13813         strcat(bookMove, bookHit);
13814         HandleMachineMove(bookMove, &first);
13815     }
13816 }
13817
13818 void
13819 MachineBlackEvent ()
13820 {
13821   char buf[MSG_SIZ];
13822   char *bookHit = NULL;
13823
13824     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13825         return;
13826
13827
13828     if (gameMode == PlayFromGameFile ||
13829         gameMode == TwoMachinesPlay  ||
13830         gameMode == Training         ||
13831         gameMode == AnalyzeMode      ||
13832         gameMode == EndOfGame)
13833         EditGameEvent();
13834
13835     if (gameMode == EditPosition)
13836         EditPositionDone(TRUE);
13837
13838     if (WhiteOnMove(currentMove)) {
13839         DisplayError(_("It is not Black's turn"), 0);
13840         return;
13841     }
13842
13843     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13844       ExitAnalyzeMode();
13845
13846     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13847         gameMode == AnalyzeFile)
13848         TruncateGame();
13849
13850     ResurrectChessProgram();    /* in case it isn't running */
13851     gameMode = MachinePlaysBlack;
13852     pausing = FALSE;
13853     ModeHighlight();
13854     SetGameInfo();
13855     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13856     DisplayTitle(buf);
13857     if (first.sendName) {
13858       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13859       SendToProgram(buf, &first);
13860     }
13861     if (first.sendTime) {
13862       if (first.useColors) {
13863         SendToProgram("white\n", &first); /*gnu kludge*/
13864       }
13865       SendTimeRemaining(&first, FALSE);
13866     }
13867     if (first.useColors) {
13868       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13869     }
13870     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13871     SetMachineThinkingEnables();
13872     first.maybeThinking = TRUE;
13873     StartClocks();
13874
13875     if (appData.autoFlipView && flipView) {
13876       flipView = !flipView;
13877       DrawPosition(FALSE, NULL);
13878       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13879     }
13880     if(bookHit) { // [HGM] book: simulate book reply
13881         static char bookMove[MSG_SIZ]; // a bit generous?
13882
13883         programStats.nodes = programStats.depth = programStats.time =
13884         programStats.score = programStats.got_only_move = 0;
13885         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13886
13887         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13888         strcat(bookMove, bookHit);
13889         HandleMachineMove(bookMove, &first);
13890     }
13891 }
13892
13893
13894 void
13895 DisplayTwoMachinesTitle ()
13896 {
13897     char buf[MSG_SIZ];
13898     if (appData.matchGames > 0) {
13899         if(appData.tourneyFile[0]) {
13900           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13901                    gameInfo.white, _("vs."), gameInfo.black,
13902                    nextGame+1, appData.matchGames+1,
13903                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13904         } else
13905         if (first.twoMachinesColor[0] == 'w') {
13906           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13907                    gameInfo.white, _("vs."),  gameInfo.black,
13908                    first.matchWins, second.matchWins,
13909                    matchGame - 1 - (first.matchWins + second.matchWins));
13910         } else {
13911           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13912                    gameInfo.white, _("vs."), gameInfo.black,
13913                    second.matchWins, first.matchWins,
13914                    matchGame - 1 - (first.matchWins + second.matchWins));
13915         }
13916     } else {
13917       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13918     }
13919     DisplayTitle(buf);
13920 }
13921
13922 void
13923 SettingsMenuIfReady ()
13924 {
13925   if (second.lastPing != second.lastPong) {
13926     DisplayMessage("", _("Waiting for second chess program"));
13927     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13928     return;
13929   }
13930   ThawUI();
13931   DisplayMessage("", "");
13932   SettingsPopUp(&second);
13933 }
13934
13935 int
13936 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13937 {
13938     char buf[MSG_SIZ];
13939     if (cps->pr == NoProc) {
13940         StartChessProgram(cps);
13941         if (cps->protocolVersion == 1) {
13942           retry();
13943           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13944         } else {
13945           /* kludge: allow timeout for initial "feature" command */
13946           if(retry != TwoMachinesEventIfReady) FreezeUI();
13947           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13948           DisplayMessage("", buf);
13949           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13950         }
13951         return 1;
13952     }
13953     return 0;
13954 }
13955
13956 void
13957 TwoMachinesEvent P((void))
13958 {
13959     int i;
13960     char buf[MSG_SIZ];
13961     ChessProgramState *onmove;
13962     char *bookHit = NULL;
13963     static int stalling = 0;
13964     TimeMark now;
13965     long wait;
13966
13967     if (appData.noChessProgram) return;
13968
13969     switch (gameMode) {
13970       case TwoMachinesPlay:
13971         return;
13972       case MachinePlaysWhite:
13973       case MachinePlaysBlack:
13974         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13975             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13976             return;
13977         }
13978         /* fall through */
13979       case BeginningOfGame:
13980       case PlayFromGameFile:
13981       case EndOfGame:
13982         EditGameEvent();
13983         if (gameMode != EditGame) return;
13984         break;
13985       case EditPosition:
13986         EditPositionDone(TRUE);
13987         break;
13988       case AnalyzeMode:
13989       case AnalyzeFile:
13990         ExitAnalyzeMode();
13991         break;
13992       case EditGame:
13993       default:
13994         break;
13995     }
13996
13997 //    forwardMostMove = currentMove;
13998     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13999     startingEngine = TRUE;
14000
14001     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14002
14003     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14004     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14005       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14006       return;
14007     }
14008     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14009
14010     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14011         startingEngine = FALSE;
14012         DisplayError("second engine does not play this", 0);
14013         return;
14014     }
14015
14016     if(!stalling) {
14017       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14018       SendToProgram("force\n", &second);
14019       stalling = 1;
14020       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14021       return;
14022     }
14023     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14024     if(appData.matchPause>10000 || appData.matchPause<10)
14025                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14026     wait = SubtractTimeMarks(&now, &pauseStart);
14027     if(wait < appData.matchPause) {
14028         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14029         return;
14030     }
14031     // we are now committed to starting the game
14032     stalling = 0;
14033     DisplayMessage("", "");
14034     if (startedFromSetupPosition) {
14035         SendBoard(&second, backwardMostMove);
14036     if (appData.debugMode) {
14037         fprintf(debugFP, "Two Machines\n");
14038     }
14039     }
14040     for (i = backwardMostMove; i < forwardMostMove; i++) {
14041         SendMoveToProgram(i, &second);
14042     }
14043
14044     gameMode = TwoMachinesPlay;
14045     pausing = startingEngine = FALSE;
14046     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14047     SetGameInfo();
14048     DisplayTwoMachinesTitle();
14049     firstMove = TRUE;
14050     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14051         onmove = &first;
14052     } else {
14053         onmove = &second;
14054     }
14055     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14056     SendToProgram(first.computerString, &first);
14057     if (first.sendName) {
14058       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14059       SendToProgram(buf, &first);
14060     }
14061     SendToProgram(second.computerString, &second);
14062     if (second.sendName) {
14063       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14064       SendToProgram(buf, &second);
14065     }
14066
14067     ResetClocks();
14068     if (!first.sendTime || !second.sendTime) {
14069         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14070         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14071     }
14072     if (onmove->sendTime) {
14073       if (onmove->useColors) {
14074         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14075       }
14076       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14077     }
14078     if (onmove->useColors) {
14079       SendToProgram(onmove->twoMachinesColor, onmove);
14080     }
14081     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14082 //    SendToProgram("go\n", onmove);
14083     onmove->maybeThinking = TRUE;
14084     SetMachineThinkingEnables();
14085
14086     StartClocks();
14087
14088     if(bookHit) { // [HGM] book: simulate book reply
14089         static char bookMove[MSG_SIZ]; // a bit generous?
14090
14091         programStats.nodes = programStats.depth = programStats.time =
14092         programStats.score = programStats.got_only_move = 0;
14093         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14094
14095         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14096         strcat(bookMove, bookHit);
14097         savedMessage = bookMove; // args for deferred call
14098         savedState = onmove;
14099         ScheduleDelayedEvent(DeferredBookMove, 1);
14100     }
14101 }
14102
14103 void
14104 TrainingEvent ()
14105 {
14106     if (gameMode == Training) {
14107       SetTrainingModeOff();
14108       gameMode = PlayFromGameFile;
14109       DisplayMessage("", _("Training mode off"));
14110     } else {
14111       gameMode = Training;
14112       animateTraining = appData.animate;
14113
14114       /* make sure we are not already at the end of the game */
14115       if (currentMove < forwardMostMove) {
14116         SetTrainingModeOn();
14117         DisplayMessage("", _("Training mode on"));
14118       } else {
14119         gameMode = PlayFromGameFile;
14120         DisplayError(_("Already at end of game"), 0);
14121       }
14122     }
14123     ModeHighlight();
14124 }
14125
14126 void
14127 IcsClientEvent ()
14128 {
14129     if (!appData.icsActive) return;
14130     switch (gameMode) {
14131       case IcsPlayingWhite:
14132       case IcsPlayingBlack:
14133       case IcsObserving:
14134       case IcsIdle:
14135       case BeginningOfGame:
14136       case IcsExamining:
14137         return;
14138
14139       case EditGame:
14140         break;
14141
14142       case EditPosition:
14143         EditPositionDone(TRUE);
14144         break;
14145
14146       case AnalyzeMode:
14147       case AnalyzeFile:
14148         ExitAnalyzeMode();
14149         break;
14150
14151       default:
14152         EditGameEvent();
14153         break;
14154     }
14155
14156     gameMode = IcsIdle;
14157     ModeHighlight();
14158     return;
14159 }
14160
14161 void
14162 EditGameEvent ()
14163 {
14164     int i;
14165
14166     switch (gameMode) {
14167       case Training:
14168         SetTrainingModeOff();
14169         break;
14170       case MachinePlaysWhite:
14171       case MachinePlaysBlack:
14172       case BeginningOfGame:
14173         SendToProgram("force\n", &first);
14174         SetUserThinkingEnables();
14175         break;
14176       case PlayFromGameFile:
14177         (void) StopLoadGameTimer();
14178         if (gameFileFP != NULL) {
14179             gameFileFP = NULL;
14180         }
14181         break;
14182       case EditPosition:
14183         EditPositionDone(TRUE);
14184         break;
14185       case AnalyzeMode:
14186       case AnalyzeFile:
14187         ExitAnalyzeMode();
14188         SendToProgram("force\n", &first);
14189         break;
14190       case TwoMachinesPlay:
14191         GameEnds(EndOfFile, NULL, GE_PLAYER);
14192         ResurrectChessProgram();
14193         SetUserThinkingEnables();
14194         break;
14195       case EndOfGame:
14196         ResurrectChessProgram();
14197         break;
14198       case IcsPlayingBlack:
14199       case IcsPlayingWhite:
14200         DisplayError(_("Warning: You are still playing a game"), 0);
14201         break;
14202       case IcsObserving:
14203         DisplayError(_("Warning: You are still observing a game"), 0);
14204         break;
14205       case IcsExamining:
14206         DisplayError(_("Warning: You are still examining a game"), 0);
14207         break;
14208       case IcsIdle:
14209         break;
14210       case EditGame:
14211       default:
14212         return;
14213     }
14214
14215     pausing = FALSE;
14216     StopClocks();
14217     first.offeredDraw = second.offeredDraw = 0;
14218
14219     if (gameMode == PlayFromGameFile) {
14220         whiteTimeRemaining = timeRemaining[0][currentMove];
14221         blackTimeRemaining = timeRemaining[1][currentMove];
14222         DisplayTitle("");
14223     }
14224
14225     if (gameMode == MachinePlaysWhite ||
14226         gameMode == MachinePlaysBlack ||
14227         gameMode == TwoMachinesPlay ||
14228         gameMode == EndOfGame) {
14229         i = forwardMostMove;
14230         while (i > currentMove) {
14231             SendToProgram("undo\n", &first);
14232             i--;
14233         }
14234         if(!adjustedClock) {
14235         whiteTimeRemaining = timeRemaining[0][currentMove];
14236         blackTimeRemaining = timeRemaining[1][currentMove];
14237         DisplayBothClocks();
14238         }
14239         if (whiteFlag || blackFlag) {
14240             whiteFlag = blackFlag = 0;
14241         }
14242         DisplayTitle("");
14243     }
14244
14245     gameMode = EditGame;
14246     ModeHighlight();
14247     SetGameInfo();
14248 }
14249
14250
14251 void
14252 EditPositionEvent ()
14253 {
14254     if (gameMode == EditPosition) {
14255         EditGameEvent();
14256         return;
14257     }
14258
14259     EditGameEvent();
14260     if (gameMode != EditGame) return;
14261
14262     gameMode = EditPosition;
14263     ModeHighlight();
14264     SetGameInfo();
14265     if (currentMove > 0)
14266       CopyBoard(boards[0], boards[currentMove]);
14267
14268     blackPlaysFirst = !WhiteOnMove(currentMove);
14269     ResetClocks();
14270     currentMove = forwardMostMove = backwardMostMove = 0;
14271     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14272     DisplayMove(-1);
14273     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14274 }
14275
14276 void
14277 ExitAnalyzeMode ()
14278 {
14279     /* [DM] icsEngineAnalyze - possible call from other functions */
14280     if (appData.icsEngineAnalyze) {
14281         appData.icsEngineAnalyze = FALSE;
14282
14283         DisplayMessage("",_("Close ICS engine analyze..."));
14284     }
14285     if (first.analysisSupport && first.analyzing) {
14286       SendToBoth("exit\n");
14287       first.analyzing = second.analyzing = FALSE;
14288     }
14289     thinkOutput[0] = NULLCHAR;
14290 }
14291
14292 void
14293 EditPositionDone (Boolean fakeRights)
14294 {
14295     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14296
14297     startedFromSetupPosition = TRUE;
14298     InitChessProgram(&first, FALSE);
14299     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14300       boards[0][EP_STATUS] = EP_NONE;
14301       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14302       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14303         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14304         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14305       } else boards[0][CASTLING][2] = NoRights;
14306       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14307         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14308         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14309       } else boards[0][CASTLING][5] = NoRights;
14310       if(gameInfo.variant == VariantSChess) {
14311         int i;
14312         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14313           boards[0][VIRGIN][i] = 0;
14314           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14315           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14316         }
14317       }
14318     }
14319     SendToProgram("force\n", &first);
14320     if (blackPlaysFirst) {
14321         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14322         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14323         currentMove = forwardMostMove = backwardMostMove = 1;
14324         CopyBoard(boards[1], boards[0]);
14325     } else {
14326         currentMove = forwardMostMove = backwardMostMove = 0;
14327     }
14328     SendBoard(&first, forwardMostMove);
14329     if (appData.debugMode) {
14330         fprintf(debugFP, "EditPosDone\n");
14331     }
14332     DisplayTitle("");
14333     DisplayMessage("", "");
14334     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14335     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14336     gameMode = EditGame;
14337     ModeHighlight();
14338     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14339     ClearHighlights(); /* [AS] */
14340 }
14341
14342 /* Pause for `ms' milliseconds */
14343 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14344 void
14345 TimeDelay (long ms)
14346 {
14347     TimeMark m1, m2;
14348
14349     GetTimeMark(&m1);
14350     do {
14351         GetTimeMark(&m2);
14352     } while (SubtractTimeMarks(&m2, &m1) < ms);
14353 }
14354
14355 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14356 void
14357 SendMultiLineToICS (char *buf)
14358 {
14359     char temp[MSG_SIZ+1], *p;
14360     int len;
14361
14362     len = strlen(buf);
14363     if (len > MSG_SIZ)
14364       len = MSG_SIZ;
14365
14366     strncpy(temp, buf, len);
14367     temp[len] = 0;
14368
14369     p = temp;
14370     while (*p) {
14371         if (*p == '\n' || *p == '\r')
14372           *p = ' ';
14373         ++p;
14374     }
14375
14376     strcat(temp, "\n");
14377     SendToICS(temp);
14378     SendToPlayer(temp, strlen(temp));
14379 }
14380
14381 void
14382 SetWhiteToPlayEvent ()
14383 {
14384     if (gameMode == EditPosition) {
14385         blackPlaysFirst = FALSE;
14386         DisplayBothClocks();    /* works because currentMove is 0 */
14387     } else if (gameMode == IcsExamining) {
14388         SendToICS(ics_prefix);
14389         SendToICS("tomove white\n");
14390     }
14391 }
14392
14393 void
14394 SetBlackToPlayEvent ()
14395 {
14396     if (gameMode == EditPosition) {
14397         blackPlaysFirst = TRUE;
14398         currentMove = 1;        /* kludge */
14399         DisplayBothClocks();
14400         currentMove = 0;
14401     } else if (gameMode == IcsExamining) {
14402         SendToICS(ics_prefix);
14403         SendToICS("tomove black\n");
14404     }
14405 }
14406
14407 void
14408 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14409 {
14410     char buf[MSG_SIZ];
14411     ChessSquare piece = boards[0][y][x];
14412
14413     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14414
14415     switch (selection) {
14416       case ClearBoard:
14417         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14418             SendToICS(ics_prefix);
14419             SendToICS("bsetup clear\n");
14420         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14421             SendToICS(ics_prefix);
14422             SendToICS("clearboard\n");
14423         } else {
14424             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14425                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14426                 for (y = 0; y < BOARD_HEIGHT; y++) {
14427                     if (gameMode == IcsExamining) {
14428                         if (boards[currentMove][y][x] != EmptySquare) {
14429                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14430                                     AAA + x, ONE + y);
14431                             SendToICS(buf);
14432                         }
14433                     } else {
14434                         boards[0][y][x] = p;
14435                     }
14436                 }
14437             }
14438         }
14439         if (gameMode == EditPosition) {
14440             DrawPosition(FALSE, boards[0]);
14441         }
14442         break;
14443
14444       case WhitePlay:
14445         SetWhiteToPlayEvent();
14446         break;
14447
14448       case BlackPlay:
14449         SetBlackToPlayEvent();
14450         break;
14451
14452       case EmptySquare:
14453         if (gameMode == IcsExamining) {
14454             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14455             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14456             SendToICS(buf);
14457         } else {
14458             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14459                 if(x == BOARD_LEFT-2) {
14460                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14461                     boards[0][y][1] = 0;
14462                 } else
14463                 if(x == BOARD_RGHT+1) {
14464                     if(y >= gameInfo.holdingsSize) break;
14465                     boards[0][y][BOARD_WIDTH-2] = 0;
14466                 } else break;
14467             }
14468             boards[0][y][x] = EmptySquare;
14469             DrawPosition(FALSE, boards[0]);
14470         }
14471         break;
14472
14473       case PromotePiece:
14474         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14475            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14476             selection = (ChessSquare) (PROMOTED piece);
14477         } else if(piece == EmptySquare) selection = WhiteSilver;
14478         else selection = (ChessSquare)((int)piece - 1);
14479         goto defaultlabel;
14480
14481       case DemotePiece:
14482         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14483            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14484             selection = (ChessSquare) (DEMOTED piece);
14485         } else if(piece == EmptySquare) selection = BlackSilver;
14486         else selection = (ChessSquare)((int)piece + 1);
14487         goto defaultlabel;
14488
14489       case WhiteQueen:
14490       case BlackQueen:
14491         if(gameInfo.variant == VariantShatranj ||
14492            gameInfo.variant == VariantXiangqi  ||
14493            gameInfo.variant == VariantCourier  ||
14494            gameInfo.variant == VariantMakruk     )
14495             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14496         goto defaultlabel;
14497
14498       case WhiteKing:
14499       case BlackKing:
14500         if(gameInfo.variant == VariantXiangqi)
14501             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14502         if(gameInfo.variant == VariantKnightmate)
14503             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14504       default:
14505         defaultlabel:
14506         if (gameMode == IcsExamining) {
14507             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14508             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14509                      PieceToChar(selection), AAA + x, ONE + y);
14510             SendToICS(buf);
14511         } else {
14512             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14513                 int n;
14514                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14515                     n = PieceToNumber(selection - BlackPawn);
14516                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14517                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14518                     boards[0][BOARD_HEIGHT-1-n][1]++;
14519                 } else
14520                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14521                     n = PieceToNumber(selection);
14522                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14523                     boards[0][n][BOARD_WIDTH-1] = selection;
14524                     boards[0][n][BOARD_WIDTH-2]++;
14525                 }
14526             } else
14527             boards[0][y][x] = selection;
14528             DrawPosition(TRUE, boards[0]);
14529             ClearHighlights();
14530             fromX = fromY = -1;
14531         }
14532         break;
14533     }
14534 }
14535
14536
14537 void
14538 DropMenuEvent (ChessSquare selection, int x, int y)
14539 {
14540     ChessMove moveType;
14541
14542     switch (gameMode) {
14543       case IcsPlayingWhite:
14544       case MachinePlaysBlack:
14545         if (!WhiteOnMove(currentMove)) {
14546             DisplayMoveError(_("It is Black's turn"));
14547             return;
14548         }
14549         moveType = WhiteDrop;
14550         break;
14551       case IcsPlayingBlack:
14552       case MachinePlaysWhite:
14553         if (WhiteOnMove(currentMove)) {
14554             DisplayMoveError(_("It is White's turn"));
14555             return;
14556         }
14557         moveType = BlackDrop;
14558         break;
14559       case EditGame:
14560         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14561         break;
14562       default:
14563         return;
14564     }
14565
14566     if (moveType == BlackDrop && selection < BlackPawn) {
14567       selection = (ChessSquare) ((int) selection
14568                                  + (int) BlackPawn - (int) WhitePawn);
14569     }
14570     if (boards[currentMove][y][x] != EmptySquare) {
14571         DisplayMoveError(_("That square is occupied"));
14572         return;
14573     }
14574
14575     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14576 }
14577
14578 void
14579 AcceptEvent ()
14580 {
14581     /* Accept a pending offer of any kind from opponent */
14582
14583     if (appData.icsActive) {
14584         SendToICS(ics_prefix);
14585         SendToICS("accept\n");
14586     } else if (cmailMsgLoaded) {
14587         if (currentMove == cmailOldMove &&
14588             commentList[cmailOldMove] != NULL &&
14589             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14590                    "Black offers a draw" : "White offers a draw")) {
14591             TruncateGame();
14592             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14593             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14594         } else {
14595             DisplayError(_("There is no pending offer on this move"), 0);
14596             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14597         }
14598     } else {
14599         /* Not used for offers from chess program */
14600     }
14601 }
14602
14603 void
14604 DeclineEvent ()
14605 {
14606     /* Decline a pending offer of any kind from opponent */
14607
14608     if (appData.icsActive) {
14609         SendToICS(ics_prefix);
14610         SendToICS("decline\n");
14611     } else if (cmailMsgLoaded) {
14612         if (currentMove == cmailOldMove &&
14613             commentList[cmailOldMove] != NULL &&
14614             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14615                    "Black offers a draw" : "White offers a draw")) {
14616 #ifdef NOTDEF
14617             AppendComment(cmailOldMove, "Draw declined", TRUE);
14618             DisplayComment(cmailOldMove - 1, "Draw declined");
14619 #endif /*NOTDEF*/
14620         } else {
14621             DisplayError(_("There is no pending offer on this move"), 0);
14622         }
14623     } else {
14624         /* Not used for offers from chess program */
14625     }
14626 }
14627
14628 void
14629 RematchEvent ()
14630 {
14631     /* Issue ICS rematch command */
14632     if (appData.icsActive) {
14633         SendToICS(ics_prefix);
14634         SendToICS("rematch\n");
14635     }
14636 }
14637
14638 void
14639 CallFlagEvent ()
14640 {
14641     /* Call your opponent's flag (claim a win on time) */
14642     if (appData.icsActive) {
14643         SendToICS(ics_prefix);
14644         SendToICS("flag\n");
14645     } else {
14646         switch (gameMode) {
14647           default:
14648             return;
14649           case MachinePlaysWhite:
14650             if (whiteFlag) {
14651                 if (blackFlag)
14652                   GameEnds(GameIsDrawn, "Both players ran out of time",
14653                            GE_PLAYER);
14654                 else
14655                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14656             } else {
14657                 DisplayError(_("Your opponent is not out of time"), 0);
14658             }
14659             break;
14660           case MachinePlaysBlack:
14661             if (blackFlag) {
14662                 if (whiteFlag)
14663                   GameEnds(GameIsDrawn, "Both players ran out of time",
14664                            GE_PLAYER);
14665                 else
14666                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14667             } else {
14668                 DisplayError(_("Your opponent is not out of time"), 0);
14669             }
14670             break;
14671         }
14672     }
14673 }
14674
14675 void
14676 ClockClick (int which)
14677 {       // [HGM] code moved to back-end from winboard.c
14678         if(which) { // black clock
14679           if (gameMode == EditPosition || gameMode == IcsExamining) {
14680             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14681             SetBlackToPlayEvent();
14682           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14683           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14684           } else if (shiftKey) {
14685             AdjustClock(which, -1);
14686           } else if (gameMode == IcsPlayingWhite ||
14687                      gameMode == MachinePlaysBlack) {
14688             CallFlagEvent();
14689           }
14690         } else { // white clock
14691           if (gameMode == EditPosition || gameMode == IcsExamining) {
14692             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14693             SetWhiteToPlayEvent();
14694           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14695           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14696           } else if (shiftKey) {
14697             AdjustClock(which, -1);
14698           } else if (gameMode == IcsPlayingBlack ||
14699                    gameMode == MachinePlaysWhite) {
14700             CallFlagEvent();
14701           }
14702         }
14703 }
14704
14705 void
14706 DrawEvent ()
14707 {
14708     /* Offer draw or accept pending draw offer from opponent */
14709
14710     if (appData.icsActive) {
14711         /* Note: tournament rules require draw offers to be
14712            made after you make your move but before you punch
14713            your clock.  Currently ICS doesn't let you do that;
14714            instead, you immediately punch your clock after making
14715            a move, but you can offer a draw at any time. */
14716
14717         SendToICS(ics_prefix);
14718         SendToICS("draw\n");
14719         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14720     } else if (cmailMsgLoaded) {
14721         if (currentMove == cmailOldMove &&
14722             commentList[cmailOldMove] != NULL &&
14723             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14724                    "Black offers a draw" : "White offers a draw")) {
14725             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14726             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14727         } else if (currentMove == cmailOldMove + 1) {
14728             char *offer = WhiteOnMove(cmailOldMove) ?
14729               "White offers a draw" : "Black offers a draw";
14730             AppendComment(currentMove, offer, TRUE);
14731             DisplayComment(currentMove - 1, offer);
14732             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14733         } else {
14734             DisplayError(_("You must make your move before offering a draw"), 0);
14735             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14736         }
14737     } else if (first.offeredDraw) {
14738         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14739     } else {
14740         if (first.sendDrawOffers) {
14741             SendToProgram("draw\n", &first);
14742             userOfferedDraw = TRUE;
14743         }
14744     }
14745 }
14746
14747 void
14748 AdjournEvent ()
14749 {
14750     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14751
14752     if (appData.icsActive) {
14753         SendToICS(ics_prefix);
14754         SendToICS("adjourn\n");
14755     } else {
14756         /* Currently GNU Chess doesn't offer or accept Adjourns */
14757     }
14758 }
14759
14760
14761 void
14762 AbortEvent ()
14763 {
14764     /* Offer Abort or accept pending Abort offer from opponent */
14765
14766     if (appData.icsActive) {
14767         SendToICS(ics_prefix);
14768         SendToICS("abort\n");
14769     } else {
14770         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14771     }
14772 }
14773
14774 void
14775 ResignEvent ()
14776 {
14777     /* Resign.  You can do this even if it's not your turn. */
14778
14779     if (appData.icsActive) {
14780         SendToICS(ics_prefix);
14781         SendToICS("resign\n");
14782     } else {
14783         switch (gameMode) {
14784           case MachinePlaysWhite:
14785             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14786             break;
14787           case MachinePlaysBlack:
14788             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14789             break;
14790           case EditGame:
14791             if (cmailMsgLoaded) {
14792                 TruncateGame();
14793                 if (WhiteOnMove(cmailOldMove)) {
14794                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14795                 } else {
14796                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14797                 }
14798                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14799             }
14800             break;
14801           default:
14802             break;
14803         }
14804     }
14805 }
14806
14807
14808 void
14809 StopObservingEvent ()
14810 {
14811     /* Stop observing current games */
14812     SendToICS(ics_prefix);
14813     SendToICS("unobserve\n");
14814 }
14815
14816 void
14817 StopExaminingEvent ()
14818 {
14819     /* Stop observing current game */
14820     SendToICS(ics_prefix);
14821     SendToICS("unexamine\n");
14822 }
14823
14824 void
14825 ForwardInner (int target)
14826 {
14827     int limit; int oldSeekGraphUp = seekGraphUp;
14828
14829     if (appData.debugMode)
14830         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14831                 target, currentMove, forwardMostMove);
14832
14833     if (gameMode == EditPosition)
14834       return;
14835
14836     seekGraphUp = FALSE;
14837     MarkTargetSquares(1);
14838
14839     if (gameMode == PlayFromGameFile && !pausing)
14840       PauseEvent();
14841
14842     if (gameMode == IcsExamining && pausing)
14843       limit = pauseExamForwardMostMove;
14844     else
14845       limit = forwardMostMove;
14846
14847     if (target > limit) target = limit;
14848
14849     if (target > 0 && moveList[target - 1][0]) {
14850         int fromX, fromY, toX, toY;
14851         toX = moveList[target - 1][2] - AAA;
14852         toY = moveList[target - 1][3] - ONE;
14853         if (moveList[target - 1][1] == '@') {
14854             if (appData.highlightLastMove) {
14855                 SetHighlights(-1, -1, toX, toY);
14856             }
14857         } else {
14858             fromX = moveList[target - 1][0] - AAA;
14859             fromY = moveList[target - 1][1] - ONE;
14860             if (target == currentMove + 1) {
14861                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14862             }
14863             if (appData.highlightLastMove) {
14864                 SetHighlights(fromX, fromY, toX, toY);
14865             }
14866         }
14867     }
14868     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14869         gameMode == Training || gameMode == PlayFromGameFile ||
14870         gameMode == AnalyzeFile) {
14871         while (currentMove < target) {
14872             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14873             SendMoveToProgram(currentMove++, &first);
14874         }
14875     } else {
14876         currentMove = target;
14877     }
14878
14879     if (gameMode == EditGame || gameMode == EndOfGame) {
14880         whiteTimeRemaining = timeRemaining[0][currentMove];
14881         blackTimeRemaining = timeRemaining[1][currentMove];
14882     }
14883     DisplayBothClocks();
14884     DisplayMove(currentMove - 1);
14885     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14886     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14887     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14888         DisplayComment(currentMove - 1, commentList[currentMove]);
14889     }
14890     ClearMap(); // [HGM] exclude: invalidate map
14891 }
14892
14893
14894 void
14895 ForwardEvent ()
14896 {
14897     if (gameMode == IcsExamining && !pausing) {
14898         SendToICS(ics_prefix);
14899         SendToICS("forward\n");
14900     } else {
14901         ForwardInner(currentMove + 1);
14902     }
14903 }
14904
14905 void
14906 ToEndEvent ()
14907 {
14908     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14909         /* to optimze, we temporarily turn off analysis mode while we feed
14910          * the remaining moves to the engine. Otherwise we get analysis output
14911          * after each move.
14912          */
14913         if (first.analysisSupport) {
14914           SendToProgram("exit\nforce\n", &first);
14915           first.analyzing = FALSE;
14916         }
14917     }
14918
14919     if (gameMode == IcsExamining && !pausing) {
14920         SendToICS(ics_prefix);
14921         SendToICS("forward 999999\n");
14922     } else {
14923         ForwardInner(forwardMostMove);
14924     }
14925
14926     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14927         /* we have fed all the moves, so reactivate analysis mode */
14928         SendToProgram("analyze\n", &first);
14929         first.analyzing = TRUE;
14930         /*first.maybeThinking = TRUE;*/
14931         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14932     }
14933 }
14934
14935 void
14936 BackwardInner (int target)
14937 {
14938     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14939
14940     if (appData.debugMode)
14941         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14942                 target, currentMove, forwardMostMove);
14943
14944     if (gameMode == EditPosition) return;
14945     seekGraphUp = FALSE;
14946     MarkTargetSquares(1);
14947     if (currentMove <= backwardMostMove) {
14948         ClearHighlights();
14949         DrawPosition(full_redraw, boards[currentMove]);
14950         return;
14951     }
14952     if (gameMode == PlayFromGameFile && !pausing)
14953       PauseEvent();
14954
14955     if (moveList[target][0]) {
14956         int fromX, fromY, toX, toY;
14957         toX = moveList[target][2] - AAA;
14958         toY = moveList[target][3] - ONE;
14959         if (moveList[target][1] == '@') {
14960             if (appData.highlightLastMove) {
14961                 SetHighlights(-1, -1, toX, toY);
14962             }
14963         } else {
14964             fromX = moveList[target][0] - AAA;
14965             fromY = moveList[target][1] - ONE;
14966             if (target == currentMove - 1) {
14967                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14968             }
14969             if (appData.highlightLastMove) {
14970                 SetHighlights(fromX, fromY, toX, toY);
14971             }
14972         }
14973     }
14974     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14975         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14976         while (currentMove > target) {
14977             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14978                 // null move cannot be undone. Reload program with move history before it.
14979                 int i;
14980                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14981                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14982                 }
14983                 SendBoard(&first, i);
14984               if(second.analyzing) SendBoard(&second, i);
14985                 for(currentMove=i; currentMove<target; currentMove++) {
14986                     SendMoveToProgram(currentMove, &first);
14987                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14988                 }
14989                 break;
14990             }
14991             SendToBoth("undo\n");
14992             currentMove--;
14993         }
14994     } else {
14995         currentMove = target;
14996     }
14997
14998     if (gameMode == EditGame || gameMode == EndOfGame) {
14999         whiteTimeRemaining = timeRemaining[0][currentMove];
15000         blackTimeRemaining = timeRemaining[1][currentMove];
15001     }
15002     DisplayBothClocks();
15003     DisplayMove(currentMove - 1);
15004     DrawPosition(full_redraw, boards[currentMove]);
15005     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15006     // [HGM] PV info: routine tests if comment empty
15007     DisplayComment(currentMove - 1, commentList[currentMove]);
15008     ClearMap(); // [HGM] exclude: invalidate map
15009 }
15010
15011 void
15012 BackwardEvent ()
15013 {
15014     if (gameMode == IcsExamining && !pausing) {
15015         SendToICS(ics_prefix);
15016         SendToICS("backward\n");
15017     } else {
15018         BackwardInner(currentMove - 1);
15019     }
15020 }
15021
15022 void
15023 ToStartEvent ()
15024 {
15025     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15026         /* to optimize, we temporarily turn off analysis mode while we undo
15027          * all the moves. Otherwise we get analysis output after each undo.
15028          */
15029         if (first.analysisSupport) {
15030           SendToProgram("exit\nforce\n", &first);
15031           first.analyzing = FALSE;
15032         }
15033     }
15034
15035     if (gameMode == IcsExamining && !pausing) {
15036         SendToICS(ics_prefix);
15037         SendToICS("backward 999999\n");
15038     } else {
15039         BackwardInner(backwardMostMove);
15040     }
15041
15042     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15043         /* we have fed all the moves, so reactivate analysis mode */
15044         SendToProgram("analyze\n", &first);
15045         first.analyzing = TRUE;
15046         /*first.maybeThinking = TRUE;*/
15047         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15048     }
15049 }
15050
15051 void
15052 ToNrEvent (int to)
15053 {
15054   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15055   if (to >= forwardMostMove) to = forwardMostMove;
15056   if (to <= backwardMostMove) to = backwardMostMove;
15057   if (to < currentMove) {
15058     BackwardInner(to);
15059   } else {
15060     ForwardInner(to);
15061   }
15062 }
15063
15064 void
15065 RevertEvent (Boolean annotate)
15066 {
15067     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15068         return;
15069     }
15070     if (gameMode != IcsExamining) {
15071         DisplayError(_("You are not examining a game"), 0);
15072         return;
15073     }
15074     if (pausing) {
15075         DisplayError(_("You can't revert while pausing"), 0);
15076         return;
15077     }
15078     SendToICS(ics_prefix);
15079     SendToICS("revert\n");
15080 }
15081
15082 void
15083 RetractMoveEvent ()
15084 {
15085     switch (gameMode) {
15086       case MachinePlaysWhite:
15087       case MachinePlaysBlack:
15088         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15089             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15090             return;
15091         }
15092         if (forwardMostMove < 2) return;
15093         currentMove = forwardMostMove = forwardMostMove - 2;
15094         whiteTimeRemaining = timeRemaining[0][currentMove];
15095         blackTimeRemaining = timeRemaining[1][currentMove];
15096         DisplayBothClocks();
15097         DisplayMove(currentMove - 1);
15098         ClearHighlights();/*!! could figure this out*/
15099         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15100         SendToProgram("remove\n", &first);
15101         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15102         break;
15103
15104       case BeginningOfGame:
15105       default:
15106         break;
15107
15108       case IcsPlayingWhite:
15109       case IcsPlayingBlack:
15110         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15111             SendToICS(ics_prefix);
15112             SendToICS("takeback 2\n");
15113         } else {
15114             SendToICS(ics_prefix);
15115             SendToICS("takeback 1\n");
15116         }
15117         break;
15118     }
15119 }
15120
15121 void
15122 MoveNowEvent ()
15123 {
15124     ChessProgramState *cps;
15125
15126     switch (gameMode) {
15127       case MachinePlaysWhite:
15128         if (!WhiteOnMove(forwardMostMove)) {
15129             DisplayError(_("It is your turn"), 0);
15130             return;
15131         }
15132         cps = &first;
15133         break;
15134       case MachinePlaysBlack:
15135         if (WhiteOnMove(forwardMostMove)) {
15136             DisplayError(_("It is your turn"), 0);
15137             return;
15138         }
15139         cps = &first;
15140         break;
15141       case TwoMachinesPlay:
15142         if (WhiteOnMove(forwardMostMove) ==
15143             (first.twoMachinesColor[0] == 'w')) {
15144             cps = &first;
15145         } else {
15146             cps = &second;
15147         }
15148         break;
15149       case BeginningOfGame:
15150       default:
15151         return;
15152     }
15153     SendToProgram("?\n", cps);
15154 }
15155
15156 void
15157 TruncateGameEvent ()
15158 {
15159     EditGameEvent();
15160     if (gameMode != EditGame) return;
15161     TruncateGame();
15162 }
15163
15164 void
15165 TruncateGame ()
15166 {
15167     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15168     if (forwardMostMove > currentMove) {
15169         if (gameInfo.resultDetails != NULL) {
15170             free(gameInfo.resultDetails);
15171             gameInfo.resultDetails = NULL;
15172             gameInfo.result = GameUnfinished;
15173         }
15174         forwardMostMove = currentMove;
15175         HistorySet(parseList, backwardMostMove, forwardMostMove,
15176                    currentMove-1);
15177     }
15178 }
15179
15180 void
15181 HintEvent ()
15182 {
15183     if (appData.noChessProgram) return;
15184     switch (gameMode) {
15185       case MachinePlaysWhite:
15186         if (WhiteOnMove(forwardMostMove)) {
15187             DisplayError(_("Wait until your turn"), 0);
15188             return;
15189         }
15190         break;
15191       case BeginningOfGame:
15192       case MachinePlaysBlack:
15193         if (!WhiteOnMove(forwardMostMove)) {
15194             DisplayError(_("Wait until your turn"), 0);
15195             return;
15196         }
15197         break;
15198       default:
15199         DisplayError(_("No hint available"), 0);
15200         return;
15201     }
15202     SendToProgram("hint\n", &first);
15203     hintRequested = TRUE;
15204 }
15205
15206 void
15207 CreateBookEvent ()
15208 {
15209     ListGame * lg = (ListGame *) gameList.head;
15210     FILE *f;
15211     int nItem;
15212     static int secondTime = FALSE;
15213
15214     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15215         DisplayError(_("Game list not loaded or empty"), 0);
15216         return;
15217     }
15218
15219     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15220         fclose(f);
15221         secondTime++;
15222         DisplayNote(_("Book file exists! Try again for overwrite."));
15223         return;
15224     }
15225
15226     creatingBook = TRUE;
15227     secondTime = FALSE;
15228
15229     /* Get list size */
15230     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15231         LoadGame(f, nItem, "", TRUE);
15232         AddGameToBook(TRUE);
15233         lg = (ListGame *) lg->node.succ;
15234     }
15235
15236     creatingBook = FALSE;
15237     FlushBook();
15238 }
15239
15240 void
15241 BookEvent ()
15242 {
15243     if (appData.noChessProgram) return;
15244     switch (gameMode) {
15245       case MachinePlaysWhite:
15246         if (WhiteOnMove(forwardMostMove)) {
15247             DisplayError(_("Wait until your turn"), 0);
15248             return;
15249         }
15250         break;
15251       case BeginningOfGame:
15252       case MachinePlaysBlack:
15253         if (!WhiteOnMove(forwardMostMove)) {
15254             DisplayError(_("Wait until your turn"), 0);
15255             return;
15256         }
15257         break;
15258       case EditPosition:
15259         EditPositionDone(TRUE);
15260         break;
15261       case TwoMachinesPlay:
15262         return;
15263       default:
15264         break;
15265     }
15266     SendToProgram("bk\n", &first);
15267     bookOutput[0] = NULLCHAR;
15268     bookRequested = TRUE;
15269 }
15270
15271 void
15272 AboutGameEvent ()
15273 {
15274     char *tags = PGNTags(&gameInfo);
15275     TagsPopUp(tags, CmailMsg());
15276     free(tags);
15277 }
15278
15279 /* end button procedures */
15280
15281 void
15282 PrintPosition (FILE *fp, int move)
15283 {
15284     int i, j;
15285
15286     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15287         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15288             char c = PieceToChar(boards[move][i][j]);
15289             fputc(c == 'x' ? '.' : c, fp);
15290             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15291         }
15292     }
15293     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15294       fprintf(fp, "white to play\n");
15295     else
15296       fprintf(fp, "black to play\n");
15297 }
15298
15299 void
15300 PrintOpponents (FILE *fp)
15301 {
15302     if (gameInfo.white != NULL) {
15303         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15304     } else {
15305         fprintf(fp, "\n");
15306     }
15307 }
15308
15309 /* Find last component of program's own name, using some heuristics */
15310 void
15311 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15312 {
15313     char *p, *q, c;
15314     int local = (strcmp(host, "localhost") == 0);
15315     while (!local && (p = strchr(prog, ';')) != NULL) {
15316         p++;
15317         while (*p == ' ') p++;
15318         prog = p;
15319     }
15320     if (*prog == '"' || *prog == '\'') {
15321         q = strchr(prog + 1, *prog);
15322     } else {
15323         q = strchr(prog, ' ');
15324     }
15325     if (q == NULL) q = prog + strlen(prog);
15326     p = q;
15327     while (p >= prog && *p != '/' && *p != '\\') p--;
15328     p++;
15329     if(p == prog && *p == '"') p++;
15330     c = *q; *q = 0;
15331     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15332     memcpy(buf, p, q - p);
15333     buf[q - p] = NULLCHAR;
15334     if (!local) {
15335         strcat(buf, "@");
15336         strcat(buf, host);
15337     }
15338 }
15339
15340 char *
15341 TimeControlTagValue ()
15342 {
15343     char buf[MSG_SIZ];
15344     if (!appData.clockMode) {
15345       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15346     } else if (movesPerSession > 0) {
15347       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15348     } else if (timeIncrement == 0) {
15349       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15350     } else {
15351       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15352     }
15353     return StrSave(buf);
15354 }
15355
15356 void
15357 SetGameInfo ()
15358 {
15359     /* This routine is used only for certain modes */
15360     VariantClass v = gameInfo.variant;
15361     ChessMove r = GameUnfinished;
15362     char *p = NULL;
15363
15364     if(keepInfo) return;
15365
15366     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15367         r = gameInfo.result;
15368         p = gameInfo.resultDetails;
15369         gameInfo.resultDetails = NULL;
15370     }
15371     ClearGameInfo(&gameInfo);
15372     gameInfo.variant = v;
15373
15374     switch (gameMode) {
15375       case MachinePlaysWhite:
15376         gameInfo.event = StrSave( appData.pgnEventHeader );
15377         gameInfo.site = StrSave(HostName());
15378         gameInfo.date = PGNDate();
15379         gameInfo.round = StrSave("-");
15380         gameInfo.white = StrSave(first.tidy);
15381         gameInfo.black = StrSave(UserName());
15382         gameInfo.timeControl = TimeControlTagValue();
15383         break;
15384
15385       case MachinePlaysBlack:
15386         gameInfo.event = StrSave( appData.pgnEventHeader );
15387         gameInfo.site = StrSave(HostName());
15388         gameInfo.date = PGNDate();
15389         gameInfo.round = StrSave("-");
15390         gameInfo.white = StrSave(UserName());
15391         gameInfo.black = StrSave(first.tidy);
15392         gameInfo.timeControl = TimeControlTagValue();
15393         break;
15394
15395       case TwoMachinesPlay:
15396         gameInfo.event = StrSave( appData.pgnEventHeader );
15397         gameInfo.site = StrSave(HostName());
15398         gameInfo.date = PGNDate();
15399         if (roundNr > 0) {
15400             char buf[MSG_SIZ];
15401             snprintf(buf, MSG_SIZ, "%d", roundNr);
15402             gameInfo.round = StrSave(buf);
15403         } else {
15404             gameInfo.round = StrSave("-");
15405         }
15406         if (first.twoMachinesColor[0] == 'w') {
15407             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15408             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15409         } else {
15410             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15411             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15412         }
15413         gameInfo.timeControl = TimeControlTagValue();
15414         break;
15415
15416       case EditGame:
15417         gameInfo.event = StrSave("Edited game");
15418         gameInfo.site = StrSave(HostName());
15419         gameInfo.date = PGNDate();
15420         gameInfo.round = StrSave("-");
15421         gameInfo.white = StrSave("-");
15422         gameInfo.black = StrSave("-");
15423         gameInfo.result = r;
15424         gameInfo.resultDetails = p;
15425         break;
15426
15427       case EditPosition:
15428         gameInfo.event = StrSave("Edited position");
15429         gameInfo.site = StrSave(HostName());
15430         gameInfo.date = PGNDate();
15431         gameInfo.round = StrSave("-");
15432         gameInfo.white = StrSave("-");
15433         gameInfo.black = StrSave("-");
15434         break;
15435
15436       case IcsPlayingWhite:
15437       case IcsPlayingBlack:
15438       case IcsObserving:
15439       case IcsExamining:
15440         break;
15441
15442       case PlayFromGameFile:
15443         gameInfo.event = StrSave("Game from non-PGN file");
15444         gameInfo.site = StrSave(HostName());
15445         gameInfo.date = PGNDate();
15446         gameInfo.round = StrSave("-");
15447         gameInfo.white = StrSave("?");
15448         gameInfo.black = StrSave("?");
15449         break;
15450
15451       default:
15452         break;
15453     }
15454 }
15455
15456 void
15457 ReplaceComment (int index, char *text)
15458 {
15459     int len;
15460     char *p;
15461     float score;
15462
15463     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15464        pvInfoList[index-1].depth == len &&
15465        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15466        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15467     while (*text == '\n') text++;
15468     len = strlen(text);
15469     while (len > 0 && text[len - 1] == '\n') len--;
15470
15471     if (commentList[index] != NULL)
15472       free(commentList[index]);
15473
15474     if (len == 0) {
15475         commentList[index] = NULL;
15476         return;
15477     }
15478   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15479       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15480       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15481     commentList[index] = (char *) malloc(len + 2);
15482     strncpy(commentList[index], text, len);
15483     commentList[index][len] = '\n';
15484     commentList[index][len + 1] = NULLCHAR;
15485   } else {
15486     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15487     char *p;
15488     commentList[index] = (char *) malloc(len + 7);
15489     safeStrCpy(commentList[index], "{\n", 3);
15490     safeStrCpy(commentList[index]+2, text, len+1);
15491     commentList[index][len+2] = NULLCHAR;
15492     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15493     strcat(commentList[index], "\n}\n");
15494   }
15495 }
15496
15497 void
15498 CrushCRs (char *text)
15499 {
15500   char *p = text;
15501   char *q = text;
15502   char ch;
15503
15504   do {
15505     ch = *p++;
15506     if (ch == '\r') continue;
15507     *q++ = ch;
15508   } while (ch != '\0');
15509 }
15510
15511 void
15512 AppendComment (int index, char *text, Boolean addBraces)
15513 /* addBraces  tells if we should add {} */
15514 {
15515     int oldlen, len;
15516     char *old;
15517
15518 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15519     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15520
15521     CrushCRs(text);
15522     while (*text == '\n') text++;
15523     len = strlen(text);
15524     while (len > 0 && text[len - 1] == '\n') len--;
15525     text[len] = NULLCHAR;
15526
15527     if (len == 0) return;
15528
15529     if (commentList[index] != NULL) {
15530       Boolean addClosingBrace = addBraces;
15531         old = commentList[index];
15532         oldlen = strlen(old);
15533         while(commentList[index][oldlen-1] ==  '\n')
15534           commentList[index][--oldlen] = NULLCHAR;
15535         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15536         safeStrCpy(commentList[index], old, oldlen + len + 6);
15537         free(old);
15538         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15539         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15540           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15541           while (*text == '\n') { text++; len--; }
15542           commentList[index][--oldlen] = NULLCHAR;
15543       }
15544         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15545         else          strcat(commentList[index], "\n");
15546         strcat(commentList[index], text);
15547         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15548         else          strcat(commentList[index], "\n");
15549     } else {
15550         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15551         if(addBraces)
15552           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15553         else commentList[index][0] = NULLCHAR;
15554         strcat(commentList[index], text);
15555         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15556         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15557     }
15558 }
15559
15560 static char *
15561 FindStr (char * text, char * sub_text)
15562 {
15563     char * result = strstr( text, sub_text );
15564
15565     if( result != NULL ) {
15566         result += strlen( sub_text );
15567     }
15568
15569     return result;
15570 }
15571
15572 /* [AS] Try to extract PV info from PGN comment */
15573 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15574 char *
15575 GetInfoFromComment (int index, char * text)
15576 {
15577     char * sep = text, *p;
15578
15579     if( text != NULL && index > 0 ) {
15580         int score = 0;
15581         int depth = 0;
15582         int time = -1, sec = 0, deci;
15583         char * s_eval = FindStr( text, "[%eval " );
15584         char * s_emt = FindStr( text, "[%emt " );
15585
15586         if( s_eval != NULL || s_emt != NULL ) {
15587             /* New style */
15588             char delim;
15589
15590             if( s_eval != NULL ) {
15591                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15592                     return text;
15593                 }
15594
15595                 if( delim != ']' ) {
15596                     return text;
15597                 }
15598             }
15599
15600             if( s_emt != NULL ) {
15601             }
15602                 return text;
15603         }
15604         else {
15605             /* We expect something like: [+|-]nnn.nn/dd */
15606             int score_lo = 0;
15607
15608             if(*text != '{') return text; // [HGM] braces: must be normal comment
15609
15610             sep = strchr( text, '/' );
15611             if( sep == NULL || sep < (text+4) ) {
15612                 return text;
15613             }
15614
15615             p = text;
15616             if(p[1] == '(') { // comment starts with PV
15617                p = strchr(p, ')'); // locate end of PV
15618                if(p == NULL || sep < p+5) return text;
15619                // at this point we have something like "{(.*) +0.23/6 ..."
15620                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15621                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15622                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15623             }
15624             time = -1; sec = -1; deci = -1;
15625             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15626                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15627                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15628                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15629                 return text;
15630             }
15631
15632             if( score_lo < 0 || score_lo >= 100 ) {
15633                 return text;
15634             }
15635
15636             if(sec >= 0) time = 600*time + 10*sec; else
15637             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15638
15639             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15640
15641             /* [HGM] PV time: now locate end of PV info */
15642             while( *++sep >= '0' && *sep <= '9'); // strip depth
15643             if(time >= 0)
15644             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15645             if(sec >= 0)
15646             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15647             if(deci >= 0)
15648             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15649             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15650         }
15651
15652         if( depth <= 0 ) {
15653             return text;
15654         }
15655
15656         if( time < 0 ) {
15657             time = -1;
15658         }
15659
15660         pvInfoList[index-1].depth = depth;
15661         pvInfoList[index-1].score = score;
15662         pvInfoList[index-1].time  = 10*time; // centi-sec
15663         if(*sep == '}') *sep = 0; else *--sep = '{';
15664         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15665     }
15666     return sep;
15667 }
15668
15669 void
15670 SendToProgram (char *message, ChessProgramState *cps)
15671 {
15672     int count, outCount, error;
15673     char buf[MSG_SIZ];
15674
15675     if (cps->pr == NoProc) return;
15676     Attention(cps);
15677
15678     if (appData.debugMode) {
15679         TimeMark now;
15680         GetTimeMark(&now);
15681         fprintf(debugFP, "%ld >%-6s: %s",
15682                 SubtractTimeMarks(&now, &programStartTime),
15683                 cps->which, message);
15684         if(serverFP)
15685             fprintf(serverFP, "%ld >%-6s: %s",
15686                 SubtractTimeMarks(&now, &programStartTime),
15687                 cps->which, message), fflush(serverFP);
15688     }
15689
15690     count = strlen(message);
15691     outCount = OutputToProcess(cps->pr, message, count, &error);
15692     if (outCount < count && !exiting
15693                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15694       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15695       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15696         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15697             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15698                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15699                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15700                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15701             } else {
15702                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15703                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15704                 gameInfo.result = res;
15705             }
15706             gameInfo.resultDetails = StrSave(buf);
15707         }
15708         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15709         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15710     }
15711 }
15712
15713 void
15714 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15715 {
15716     char *end_str;
15717     char buf[MSG_SIZ];
15718     ChessProgramState *cps = (ChessProgramState *)closure;
15719
15720     if (isr != cps->isr) return; /* Killed intentionally */
15721     if (count <= 0) {
15722         if (count == 0) {
15723             RemoveInputSource(cps->isr);
15724             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15725                     _(cps->which), cps->program);
15726             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15727             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15728                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15729                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15730                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15731                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15732                 } else {
15733                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15734                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15735                     gameInfo.result = res;
15736                 }
15737                 gameInfo.resultDetails = StrSave(buf);
15738             }
15739             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15740             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15741         } else {
15742             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15743                     _(cps->which), cps->program);
15744             RemoveInputSource(cps->isr);
15745
15746             /* [AS] Program is misbehaving badly... kill it */
15747             if( count == -2 ) {
15748                 DestroyChildProcess( cps->pr, 9 );
15749                 cps->pr = NoProc;
15750             }
15751
15752             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15753         }
15754         return;
15755     }
15756
15757     if ((end_str = strchr(message, '\r')) != NULL)
15758       *end_str = NULLCHAR;
15759     if ((end_str = strchr(message, '\n')) != NULL)
15760       *end_str = NULLCHAR;
15761
15762     if (appData.debugMode) {
15763         TimeMark now; int print = 1;
15764         char *quote = ""; char c; int i;
15765
15766         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15767                 char start = message[0];
15768                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15769                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15770                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15771                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15772                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15773                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15774                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15775                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15776                    sscanf(message, "hint: %c", &c)!=1 &&
15777                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15778                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15779                     print = (appData.engineComments >= 2);
15780                 }
15781                 message[0] = start; // restore original message
15782         }
15783         if(print) {
15784                 GetTimeMark(&now);
15785                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15786                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15787                         quote,
15788                         message);
15789                 if(serverFP)
15790                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15791                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15792                         quote,
15793                         message), fflush(serverFP);
15794         }
15795     }
15796
15797     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15798     if (appData.icsEngineAnalyze) {
15799         if (strstr(message, "whisper") != NULL ||
15800              strstr(message, "kibitz") != NULL ||
15801             strstr(message, "tellics") != NULL) return;
15802     }
15803
15804     HandleMachineMove(message, cps);
15805 }
15806
15807
15808 void
15809 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15810 {
15811     char buf[MSG_SIZ];
15812     int seconds;
15813
15814     if( timeControl_2 > 0 ) {
15815         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15816             tc = timeControl_2;
15817         }
15818     }
15819     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15820     inc /= cps->timeOdds;
15821     st  /= cps->timeOdds;
15822
15823     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15824
15825     if (st > 0) {
15826       /* Set exact time per move, normally using st command */
15827       if (cps->stKludge) {
15828         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15829         seconds = st % 60;
15830         if (seconds == 0) {
15831           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15832         } else {
15833           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15834         }
15835       } else {
15836         snprintf(buf, MSG_SIZ, "st %d\n", st);
15837       }
15838     } else {
15839       /* Set conventional or incremental time control, using level command */
15840       if (seconds == 0) {
15841         /* Note old gnuchess bug -- minutes:seconds used to not work.
15842            Fixed in later versions, but still avoid :seconds
15843            when seconds is 0. */
15844         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15845       } else {
15846         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15847                  seconds, inc/1000.);
15848       }
15849     }
15850     SendToProgram(buf, cps);
15851
15852     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15853     /* Orthogonally, limit search to given depth */
15854     if (sd > 0) {
15855       if (cps->sdKludge) {
15856         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15857       } else {
15858         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15859       }
15860       SendToProgram(buf, cps);
15861     }
15862
15863     if(cps->nps >= 0) { /* [HGM] nps */
15864         if(cps->supportsNPS == FALSE)
15865           cps->nps = -1; // don't use if engine explicitly says not supported!
15866         else {
15867           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15868           SendToProgram(buf, cps);
15869         }
15870     }
15871 }
15872
15873 ChessProgramState *
15874 WhitePlayer ()
15875 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15876 {
15877     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15878        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15879         return &second;
15880     return &first;
15881 }
15882
15883 void
15884 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15885 {
15886     char message[MSG_SIZ];
15887     long time, otime;
15888
15889     /* Note: this routine must be called when the clocks are stopped
15890        or when they have *just* been set or switched; otherwise
15891        it will be off by the time since the current tick started.
15892     */
15893     if (machineWhite) {
15894         time = whiteTimeRemaining / 10;
15895         otime = blackTimeRemaining / 10;
15896     } else {
15897         time = blackTimeRemaining / 10;
15898         otime = whiteTimeRemaining / 10;
15899     }
15900     /* [HGM] translate opponent's time by time-odds factor */
15901     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15902
15903     if (time <= 0) time = 1;
15904     if (otime <= 0) otime = 1;
15905
15906     snprintf(message, MSG_SIZ, "time %ld\n", time);
15907     SendToProgram(message, cps);
15908
15909     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15910     SendToProgram(message, cps);
15911 }
15912
15913 int
15914 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15915 {
15916   char buf[MSG_SIZ];
15917   int len = strlen(name);
15918   int val;
15919
15920   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15921     (*p) += len + 1;
15922     sscanf(*p, "%d", &val);
15923     *loc = (val != 0);
15924     while (**p && **p != ' ')
15925       (*p)++;
15926     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15927     SendToProgram(buf, cps);
15928     return TRUE;
15929   }
15930   return FALSE;
15931 }
15932
15933 int
15934 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15935 {
15936   char buf[MSG_SIZ];
15937   int len = strlen(name);
15938   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15939     (*p) += len + 1;
15940     sscanf(*p, "%d", loc);
15941     while (**p && **p != ' ') (*p)++;
15942     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15943     SendToProgram(buf, cps);
15944     return TRUE;
15945   }
15946   return FALSE;
15947 }
15948
15949 int
15950 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15951 {
15952   char buf[MSG_SIZ];
15953   int len = strlen(name);
15954   if (strncmp((*p), name, len) == 0
15955       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15956     (*p) += len + 2;
15957     sscanf(*p, "%[^\"]", loc);
15958     while (**p && **p != '\"') (*p)++;
15959     if (**p == '\"') (*p)++;
15960     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15961     SendToProgram(buf, cps);
15962     return TRUE;
15963   }
15964   return FALSE;
15965 }
15966
15967 int
15968 ParseOption (Option *opt, ChessProgramState *cps)
15969 // [HGM] options: process the string that defines an engine option, and determine
15970 // name, type, default value, and allowed value range
15971 {
15972         char *p, *q, buf[MSG_SIZ];
15973         int n, min = (-1)<<31, max = 1<<31, def;
15974
15975         if(p = strstr(opt->name, " -spin ")) {
15976             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15977             if(max < min) max = min; // enforce consistency
15978             if(def < min) def = min;
15979             if(def > max) def = max;
15980             opt->value = def;
15981             opt->min = min;
15982             opt->max = max;
15983             opt->type = Spin;
15984         } else if((p = strstr(opt->name, " -slider "))) {
15985             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15986             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15987             if(max < min) max = min; // enforce consistency
15988             if(def < min) def = min;
15989             if(def > max) def = max;
15990             opt->value = def;
15991             opt->min = min;
15992             opt->max = max;
15993             opt->type = Spin; // Slider;
15994         } else if((p = strstr(opt->name, " -string "))) {
15995             opt->textValue = p+9;
15996             opt->type = TextBox;
15997         } else if((p = strstr(opt->name, " -file "))) {
15998             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15999             opt->textValue = p+7;
16000             opt->type = FileName; // FileName;
16001         } else if((p = strstr(opt->name, " -path "))) {
16002             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16003             opt->textValue = p+7;
16004             opt->type = PathName; // PathName;
16005         } else if(p = strstr(opt->name, " -check ")) {
16006             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16007             opt->value = (def != 0);
16008             opt->type = CheckBox;
16009         } else if(p = strstr(opt->name, " -combo ")) {
16010             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16011             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16012             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16013             opt->value = n = 0;
16014             while(q = StrStr(q, " /// ")) {
16015                 n++; *q = 0;    // count choices, and null-terminate each of them
16016                 q += 5;
16017                 if(*q == '*') { // remember default, which is marked with * prefix
16018                     q++;
16019                     opt->value = n;
16020                 }
16021                 cps->comboList[cps->comboCnt++] = q;
16022             }
16023             cps->comboList[cps->comboCnt++] = NULL;
16024             opt->max = n + 1;
16025             opt->type = ComboBox;
16026         } else if(p = strstr(opt->name, " -button")) {
16027             opt->type = Button;
16028         } else if(p = strstr(opt->name, " -save")) {
16029             opt->type = SaveButton;
16030         } else return FALSE;
16031         *p = 0; // terminate option name
16032         // now look if the command-line options define a setting for this engine option.
16033         if(cps->optionSettings && cps->optionSettings[0])
16034             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16035         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16036           snprintf(buf, MSG_SIZ, "option %s", p);
16037                 if(p = strstr(buf, ",")) *p = 0;
16038                 if(q = strchr(buf, '=')) switch(opt->type) {
16039                     case ComboBox:
16040                         for(n=0; n<opt->max; n++)
16041                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16042                         break;
16043                     case TextBox:
16044                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16045                         break;
16046                     case Spin:
16047                     case CheckBox:
16048                         opt->value = atoi(q+1);
16049                     default:
16050                         break;
16051                 }
16052                 strcat(buf, "\n");
16053                 SendToProgram(buf, cps);
16054         }
16055         return TRUE;
16056 }
16057
16058 void
16059 FeatureDone (ChessProgramState *cps, int val)
16060 {
16061   DelayedEventCallback cb = GetDelayedEvent();
16062   if ((cb == InitBackEnd3 && cps == &first) ||
16063       (cb == SettingsMenuIfReady && cps == &second) ||
16064       (cb == LoadEngine) ||
16065       (cb == TwoMachinesEventIfReady)) {
16066     CancelDelayedEvent();
16067     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16068   }
16069   cps->initDone = val;
16070   if(val) cps->reload = FALSE;
16071 }
16072
16073 /* Parse feature command from engine */
16074 void
16075 ParseFeatures (char *args, ChessProgramState *cps)
16076 {
16077   char *p = args;
16078   char *q;
16079   int val;
16080   char buf[MSG_SIZ];
16081
16082   for (;;) {
16083     while (*p == ' ') p++;
16084     if (*p == NULLCHAR) return;
16085
16086     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16087     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16088     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16089     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16090     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16091     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16092     if (BoolFeature(&p, "reuse", &val, cps)) {
16093       /* Engine can disable reuse, but can't enable it if user said no */
16094       if (!val) cps->reuse = FALSE;
16095       continue;
16096     }
16097     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16098     if (StringFeature(&p, "myname", cps->tidy, cps)) {
16099       if (gameMode == TwoMachinesPlay) {
16100         DisplayTwoMachinesTitle();
16101       } else {
16102         DisplayTitle("");
16103       }
16104       continue;
16105     }
16106     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16107     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16108     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16109     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16110     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16111     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16112     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16113     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16114     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16115     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16116     if (IntFeature(&p, "done", &val, cps)) {
16117       FeatureDone(cps, val);
16118       continue;
16119     }
16120     /* Added by Tord: */
16121     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16122     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16123     /* End of additions by Tord */
16124
16125     /* [HGM] added features: */
16126     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16127     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16128     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16129     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16130     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16131     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16132     if (StringFeature(&p, "option", buf, cps)) {
16133         if(cps->reload) continue; // we are reloading because of xreuse
16134         FREE(cps->option[cps->nrOptions].name);
16135         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16136         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16137         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16138           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16139             SendToProgram(buf, cps);
16140             continue;
16141         }
16142         if(cps->nrOptions >= MAX_OPTIONS) {
16143             cps->nrOptions--;
16144             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16145             DisplayError(buf, 0);
16146         }
16147         continue;
16148     }
16149     /* End of additions by HGM */
16150
16151     /* unknown feature: complain and skip */
16152     q = p;
16153     while (*q && *q != '=') q++;
16154     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16155     SendToProgram(buf, cps);
16156     p = q;
16157     if (*p == '=') {
16158       p++;
16159       if (*p == '\"') {
16160         p++;
16161         while (*p && *p != '\"') p++;
16162         if (*p == '\"') p++;
16163       } else {
16164         while (*p && *p != ' ') p++;
16165       }
16166     }
16167   }
16168
16169 }
16170
16171 void
16172 PeriodicUpdatesEvent (int newState)
16173 {
16174     if (newState == appData.periodicUpdates)
16175       return;
16176
16177     appData.periodicUpdates=newState;
16178
16179     /* Display type changes, so update it now */
16180 //    DisplayAnalysis();
16181
16182     /* Get the ball rolling again... */
16183     if (newState) {
16184         AnalysisPeriodicEvent(1);
16185         StartAnalysisClock();
16186     }
16187 }
16188
16189 void
16190 PonderNextMoveEvent (int newState)
16191 {
16192     if (newState == appData.ponderNextMove) return;
16193     if (gameMode == EditPosition) EditPositionDone(TRUE);
16194     if (newState) {
16195         SendToProgram("hard\n", &first);
16196         if (gameMode == TwoMachinesPlay) {
16197             SendToProgram("hard\n", &second);
16198         }
16199     } else {
16200         SendToProgram("easy\n", &first);
16201         thinkOutput[0] = NULLCHAR;
16202         if (gameMode == TwoMachinesPlay) {
16203             SendToProgram("easy\n", &second);
16204         }
16205     }
16206     appData.ponderNextMove = newState;
16207 }
16208
16209 void
16210 NewSettingEvent (int option, int *feature, char *command, int value)
16211 {
16212     char buf[MSG_SIZ];
16213
16214     if (gameMode == EditPosition) EditPositionDone(TRUE);
16215     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16216     if(feature == NULL || *feature) SendToProgram(buf, &first);
16217     if (gameMode == TwoMachinesPlay) {
16218         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16219     }
16220 }
16221
16222 void
16223 ShowThinkingEvent ()
16224 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16225 {
16226     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16227     int newState = appData.showThinking
16228         // [HGM] thinking: other features now need thinking output as well
16229         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16230
16231     if (oldState == newState) return;
16232     oldState = newState;
16233     if (gameMode == EditPosition) EditPositionDone(TRUE);
16234     if (oldState) {
16235         SendToProgram("post\n", &first);
16236         if (gameMode == TwoMachinesPlay) {
16237             SendToProgram("post\n", &second);
16238         }
16239     } else {
16240         SendToProgram("nopost\n", &first);
16241         thinkOutput[0] = NULLCHAR;
16242         if (gameMode == TwoMachinesPlay) {
16243             SendToProgram("nopost\n", &second);
16244         }
16245     }
16246 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16247 }
16248
16249 void
16250 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16251 {
16252   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16253   if (pr == NoProc) return;
16254   AskQuestion(title, question, replyPrefix, pr);
16255 }
16256
16257 void
16258 TypeInEvent (char firstChar)
16259 {
16260     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16261         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16262         gameMode == AnalyzeMode || gameMode == EditGame ||
16263         gameMode == EditPosition || gameMode == IcsExamining ||
16264         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16265         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16266                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16267                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16268         gameMode == Training) PopUpMoveDialog(firstChar);
16269 }
16270
16271 void
16272 TypeInDoneEvent (char *move)
16273 {
16274         Board board;
16275         int n, fromX, fromY, toX, toY;
16276         char promoChar;
16277         ChessMove moveType;
16278
16279         // [HGM] FENedit
16280         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16281                 EditPositionPasteFEN(move);
16282                 return;
16283         }
16284         // [HGM] movenum: allow move number to be typed in any mode
16285         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16286           ToNrEvent(2*n-1);
16287           return;
16288         }
16289         // undocumented kludge: allow command-line option to be typed in!
16290         // (potentially fatal, and does not implement the effect of the option.)
16291         // should only be used for options that are values on which future decisions will be made,
16292         // and definitely not on options that would be used during initialization.
16293         if(strstr(move, "!!! -") == move) {
16294             ParseArgsFromString(move+4);
16295             return;
16296         }
16297
16298       if (gameMode != EditGame && currentMove != forwardMostMove &&
16299         gameMode != Training) {
16300         DisplayMoveError(_("Displayed move is not current"));
16301       } else {
16302         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16303           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16304         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16305         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16306           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16307           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16308         } else {
16309           DisplayMoveError(_("Could not parse move"));
16310         }
16311       }
16312 }
16313
16314 void
16315 DisplayMove (int moveNumber)
16316 {
16317     char message[MSG_SIZ];
16318     char res[MSG_SIZ];
16319     char cpThinkOutput[MSG_SIZ];
16320
16321     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16322
16323     if (moveNumber == forwardMostMove - 1 ||
16324         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16325
16326         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16327
16328         if (strchr(cpThinkOutput, '\n')) {
16329             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16330         }
16331     } else {
16332         *cpThinkOutput = NULLCHAR;
16333     }
16334
16335     /* [AS] Hide thinking from human user */
16336     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16337         *cpThinkOutput = NULLCHAR;
16338         if( thinkOutput[0] != NULLCHAR ) {
16339             int i;
16340
16341             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16342                 cpThinkOutput[i] = '.';
16343             }
16344             cpThinkOutput[i] = NULLCHAR;
16345             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16346         }
16347     }
16348
16349     if (moveNumber == forwardMostMove - 1 &&
16350         gameInfo.resultDetails != NULL) {
16351         if (gameInfo.resultDetails[0] == NULLCHAR) {
16352           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16353         } else {
16354           snprintf(res, MSG_SIZ, " {%s} %s",
16355                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16356         }
16357     } else {
16358         res[0] = NULLCHAR;
16359     }
16360
16361     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16362         DisplayMessage(res, cpThinkOutput);
16363     } else {
16364       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16365                 WhiteOnMove(moveNumber) ? " " : ".. ",
16366                 parseList[moveNumber], res);
16367         DisplayMessage(message, cpThinkOutput);
16368     }
16369 }
16370
16371 void
16372 DisplayComment (int moveNumber, char *text)
16373 {
16374     char title[MSG_SIZ];
16375
16376     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16377       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16378     } else {
16379       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16380               WhiteOnMove(moveNumber) ? " " : ".. ",
16381               parseList[moveNumber]);
16382     }
16383     if (text != NULL && (appData.autoDisplayComment || commentUp))
16384         CommentPopUp(title, text);
16385 }
16386
16387 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16388  * might be busy thinking or pondering.  It can be omitted if your
16389  * gnuchess is configured to stop thinking immediately on any user
16390  * input.  However, that gnuchess feature depends on the FIONREAD
16391  * ioctl, which does not work properly on some flavors of Unix.
16392  */
16393 void
16394 Attention (ChessProgramState *cps)
16395 {
16396 #if ATTENTION
16397     if (!cps->useSigint) return;
16398     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16399     switch (gameMode) {
16400       case MachinePlaysWhite:
16401       case MachinePlaysBlack:
16402       case TwoMachinesPlay:
16403       case IcsPlayingWhite:
16404       case IcsPlayingBlack:
16405       case AnalyzeMode:
16406       case AnalyzeFile:
16407         /* Skip if we know it isn't thinking */
16408         if (!cps->maybeThinking) return;
16409         if (appData.debugMode)
16410           fprintf(debugFP, "Interrupting %s\n", cps->which);
16411         InterruptChildProcess(cps->pr);
16412         cps->maybeThinking = FALSE;
16413         break;
16414       default:
16415         break;
16416     }
16417 #endif /*ATTENTION*/
16418 }
16419
16420 int
16421 CheckFlags ()
16422 {
16423     if (whiteTimeRemaining <= 0) {
16424         if (!whiteFlag) {
16425             whiteFlag = TRUE;
16426             if (appData.icsActive) {
16427                 if (appData.autoCallFlag &&
16428                     gameMode == IcsPlayingBlack && !blackFlag) {
16429                   SendToICS(ics_prefix);
16430                   SendToICS("flag\n");
16431                 }
16432             } else {
16433                 if (blackFlag) {
16434                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16435                 } else {
16436                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16437                     if (appData.autoCallFlag) {
16438                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16439                         return TRUE;
16440                     }
16441                 }
16442             }
16443         }
16444     }
16445     if (blackTimeRemaining <= 0) {
16446         if (!blackFlag) {
16447             blackFlag = TRUE;
16448             if (appData.icsActive) {
16449                 if (appData.autoCallFlag &&
16450                     gameMode == IcsPlayingWhite && !whiteFlag) {
16451                   SendToICS(ics_prefix);
16452                   SendToICS("flag\n");
16453                 }
16454             } else {
16455                 if (whiteFlag) {
16456                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16457                 } else {
16458                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16459                     if (appData.autoCallFlag) {
16460                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16461                         return TRUE;
16462                     }
16463                 }
16464             }
16465         }
16466     }
16467     return FALSE;
16468 }
16469
16470 void
16471 CheckTimeControl ()
16472 {
16473     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16474         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16475
16476     /*
16477      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16478      */
16479     if ( !WhiteOnMove(forwardMostMove) ) {
16480         /* White made time control */
16481         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16482         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16483         /* [HGM] time odds: correct new time quota for time odds! */
16484                                             / WhitePlayer()->timeOdds;
16485         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16486     } else {
16487         lastBlack -= blackTimeRemaining;
16488         /* Black made time control */
16489         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16490                                             / WhitePlayer()->other->timeOdds;
16491         lastWhite = whiteTimeRemaining;
16492     }
16493 }
16494
16495 void
16496 DisplayBothClocks ()
16497 {
16498     int wom = gameMode == EditPosition ?
16499       !blackPlaysFirst : WhiteOnMove(currentMove);
16500     DisplayWhiteClock(whiteTimeRemaining, wom);
16501     DisplayBlackClock(blackTimeRemaining, !wom);
16502 }
16503
16504
16505 /* Timekeeping seems to be a portability nightmare.  I think everyone
16506    has ftime(), but I'm really not sure, so I'm including some ifdefs
16507    to use other calls if you don't.  Clocks will be less accurate if
16508    you have neither ftime nor gettimeofday.
16509 */
16510
16511 /* VS 2008 requires the #include outside of the function */
16512 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16513 #include <sys/timeb.h>
16514 #endif
16515
16516 /* Get the current time as a TimeMark */
16517 void
16518 GetTimeMark (TimeMark *tm)
16519 {
16520 #if HAVE_GETTIMEOFDAY
16521
16522     struct timeval timeVal;
16523     struct timezone timeZone;
16524
16525     gettimeofday(&timeVal, &timeZone);
16526     tm->sec = (long) timeVal.tv_sec;
16527     tm->ms = (int) (timeVal.tv_usec / 1000L);
16528
16529 #else /*!HAVE_GETTIMEOFDAY*/
16530 #if HAVE_FTIME
16531
16532 // include <sys/timeb.h> / moved to just above start of function
16533     struct timeb timeB;
16534
16535     ftime(&timeB);
16536     tm->sec = (long) timeB.time;
16537     tm->ms = (int) timeB.millitm;
16538
16539 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16540     tm->sec = (long) time(NULL);
16541     tm->ms = 0;
16542 #endif
16543 #endif
16544 }
16545
16546 /* Return the difference in milliseconds between two
16547    time marks.  We assume the difference will fit in a long!
16548 */
16549 long
16550 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16551 {
16552     return 1000L*(tm2->sec - tm1->sec) +
16553            (long) (tm2->ms - tm1->ms);
16554 }
16555
16556
16557 /*
16558  * Code to manage the game clocks.
16559  *
16560  * In tournament play, black starts the clock and then white makes a move.
16561  * We give the human user a slight advantage if he is playing white---the
16562  * clocks don't run until he makes his first move, so it takes zero time.
16563  * Also, we don't account for network lag, so we could get out of sync
16564  * with GNU Chess's clock -- but then, referees are always right.
16565  */
16566
16567 static TimeMark tickStartTM;
16568 static long intendedTickLength;
16569
16570 long
16571 NextTickLength (long timeRemaining)
16572 {
16573     long nominalTickLength, nextTickLength;
16574
16575     if (timeRemaining > 0L && timeRemaining <= 10000L)
16576       nominalTickLength = 100L;
16577     else
16578       nominalTickLength = 1000L;
16579     nextTickLength = timeRemaining % nominalTickLength;
16580     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16581
16582     return nextTickLength;
16583 }
16584
16585 /* Adjust clock one minute up or down */
16586 void
16587 AdjustClock (Boolean which, int dir)
16588 {
16589     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16590     if(which) blackTimeRemaining += 60000*dir;
16591     else      whiteTimeRemaining += 60000*dir;
16592     DisplayBothClocks();
16593     adjustedClock = TRUE;
16594 }
16595
16596 /* Stop clocks and reset to a fresh time control */
16597 void
16598 ResetClocks ()
16599 {
16600     (void) StopClockTimer();
16601     if (appData.icsActive) {
16602         whiteTimeRemaining = blackTimeRemaining = 0;
16603     } else if (searchTime) {
16604         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16605         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16606     } else { /* [HGM] correct new time quote for time odds */
16607         whiteTC = blackTC = fullTimeControlString;
16608         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16609         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16610     }
16611     if (whiteFlag || blackFlag) {
16612         DisplayTitle("");
16613         whiteFlag = blackFlag = FALSE;
16614     }
16615     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16616     DisplayBothClocks();
16617     adjustedClock = FALSE;
16618 }
16619
16620 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16621
16622 /* Decrement running clock by amount of time that has passed */
16623 void
16624 DecrementClocks ()
16625 {
16626     long timeRemaining;
16627     long lastTickLength, fudge;
16628     TimeMark now;
16629
16630     if (!appData.clockMode) return;
16631     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16632
16633     GetTimeMark(&now);
16634
16635     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16636
16637     /* Fudge if we woke up a little too soon */
16638     fudge = intendedTickLength - lastTickLength;
16639     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16640
16641     if (WhiteOnMove(forwardMostMove)) {
16642         if(whiteNPS >= 0) lastTickLength = 0;
16643         timeRemaining = whiteTimeRemaining -= lastTickLength;
16644         if(timeRemaining < 0 && !appData.icsActive) {
16645             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16646             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16647                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16648                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16649             }
16650         }
16651         DisplayWhiteClock(whiteTimeRemaining - fudge,
16652                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16653     } else {
16654         if(blackNPS >= 0) lastTickLength = 0;
16655         timeRemaining = blackTimeRemaining -= lastTickLength;
16656         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16657             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16658             if(suddenDeath) {
16659                 blackStartMove = forwardMostMove;
16660                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16661             }
16662         }
16663         DisplayBlackClock(blackTimeRemaining - fudge,
16664                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16665     }
16666     if (CheckFlags()) return;
16667
16668     if(twoBoards) { // count down secondary board's clocks as well
16669         activePartnerTime -= lastTickLength;
16670         partnerUp = 1;
16671         if(activePartner == 'W')
16672             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16673         else
16674             DisplayBlackClock(activePartnerTime, TRUE);
16675         partnerUp = 0;
16676     }
16677
16678     tickStartTM = now;
16679     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16680     StartClockTimer(intendedTickLength);
16681
16682     /* if the time remaining has fallen below the alarm threshold, sound the
16683      * alarm. if the alarm has sounded and (due to a takeback or time control
16684      * with increment) the time remaining has increased to a level above the
16685      * threshold, reset the alarm so it can sound again.
16686      */
16687
16688     if (appData.icsActive && appData.icsAlarm) {
16689
16690         /* make sure we are dealing with the user's clock */
16691         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16692                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16693            )) return;
16694
16695         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16696             alarmSounded = FALSE;
16697         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16698             PlayAlarmSound();
16699             alarmSounded = TRUE;
16700         }
16701     }
16702 }
16703
16704
16705 /* A player has just moved, so stop the previously running
16706    clock and (if in clock mode) start the other one.
16707    We redisplay both clocks in case we're in ICS mode, because
16708    ICS gives us an update to both clocks after every move.
16709    Note that this routine is called *after* forwardMostMove
16710    is updated, so the last fractional tick must be subtracted
16711    from the color that is *not* on move now.
16712 */
16713 void
16714 SwitchClocks (int newMoveNr)
16715 {
16716     long lastTickLength;
16717     TimeMark now;
16718     int flagged = FALSE;
16719
16720     GetTimeMark(&now);
16721
16722     if (StopClockTimer() && appData.clockMode) {
16723         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16724         if (!WhiteOnMove(forwardMostMove)) {
16725             if(blackNPS >= 0) lastTickLength = 0;
16726             blackTimeRemaining -= lastTickLength;
16727            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16728 //         if(pvInfoList[forwardMostMove].time == -1)
16729                  pvInfoList[forwardMostMove].time =               // use GUI time
16730                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16731         } else {
16732            if(whiteNPS >= 0) lastTickLength = 0;
16733            whiteTimeRemaining -= lastTickLength;
16734            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16735 //         if(pvInfoList[forwardMostMove].time == -1)
16736                  pvInfoList[forwardMostMove].time =
16737                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16738         }
16739         flagged = CheckFlags();
16740     }
16741     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16742     CheckTimeControl();
16743
16744     if (flagged || !appData.clockMode) return;
16745
16746     switch (gameMode) {
16747       case MachinePlaysBlack:
16748       case MachinePlaysWhite:
16749       case BeginningOfGame:
16750         if (pausing) return;
16751         break;
16752
16753       case EditGame:
16754       case PlayFromGameFile:
16755       case IcsExamining:
16756         return;
16757
16758       default:
16759         break;
16760     }
16761
16762     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16763         if(WhiteOnMove(forwardMostMove))
16764              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16765         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16766     }
16767
16768     tickStartTM = now;
16769     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16770       whiteTimeRemaining : blackTimeRemaining);
16771     StartClockTimer(intendedTickLength);
16772 }
16773
16774
16775 /* Stop both clocks */
16776 void
16777 StopClocks ()
16778 {
16779     long lastTickLength;
16780     TimeMark now;
16781
16782     if (!StopClockTimer()) return;
16783     if (!appData.clockMode) return;
16784
16785     GetTimeMark(&now);
16786
16787     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16788     if (WhiteOnMove(forwardMostMove)) {
16789         if(whiteNPS >= 0) lastTickLength = 0;
16790         whiteTimeRemaining -= lastTickLength;
16791         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16792     } else {
16793         if(blackNPS >= 0) lastTickLength = 0;
16794         blackTimeRemaining -= lastTickLength;
16795         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16796     }
16797     CheckFlags();
16798 }
16799
16800 /* Start clock of player on move.  Time may have been reset, so
16801    if clock is already running, stop and restart it. */
16802 void
16803 StartClocks ()
16804 {
16805     (void) StopClockTimer(); /* in case it was running already */
16806     DisplayBothClocks();
16807     if (CheckFlags()) return;
16808
16809     if (!appData.clockMode) return;
16810     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16811
16812     GetTimeMark(&tickStartTM);
16813     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16814       whiteTimeRemaining : blackTimeRemaining);
16815
16816    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16817     whiteNPS = blackNPS = -1;
16818     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16819        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16820         whiteNPS = first.nps;
16821     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16822        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16823         blackNPS = first.nps;
16824     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16825         whiteNPS = second.nps;
16826     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16827         blackNPS = second.nps;
16828     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16829
16830     StartClockTimer(intendedTickLength);
16831 }
16832
16833 char *
16834 TimeString (long ms)
16835 {
16836     long second, minute, hour, day;
16837     char *sign = "";
16838     static char buf[32];
16839
16840     if (ms > 0 && ms <= 9900) {
16841       /* convert milliseconds to tenths, rounding up */
16842       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16843
16844       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16845       return buf;
16846     }
16847
16848     /* convert milliseconds to seconds, rounding up */
16849     /* use floating point to avoid strangeness of integer division
16850        with negative dividends on many machines */
16851     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16852
16853     if (second < 0) {
16854         sign = "-";
16855         second = -second;
16856     }
16857
16858     day = second / (60 * 60 * 24);
16859     second = second % (60 * 60 * 24);
16860     hour = second / (60 * 60);
16861     second = second % (60 * 60);
16862     minute = second / 60;
16863     second = second % 60;
16864
16865     if (day > 0)
16866       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16867               sign, day, hour, minute, second);
16868     else if (hour > 0)
16869       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16870     else
16871       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16872
16873     return buf;
16874 }
16875
16876
16877 /*
16878  * This is necessary because some C libraries aren't ANSI C compliant yet.
16879  */
16880 char *
16881 StrStr (char *string, char *match)
16882 {
16883     int i, length;
16884
16885     length = strlen(match);
16886
16887     for (i = strlen(string) - length; i >= 0; i--, string++)
16888       if (!strncmp(match, string, length))
16889         return string;
16890
16891     return NULL;
16892 }
16893
16894 char *
16895 StrCaseStr (char *string, char *match)
16896 {
16897     int i, j, length;
16898
16899     length = strlen(match);
16900
16901     for (i = strlen(string) - length; i >= 0; i--, string++) {
16902         for (j = 0; j < length; j++) {
16903             if (ToLower(match[j]) != ToLower(string[j]))
16904               break;
16905         }
16906         if (j == length) return string;
16907     }
16908
16909     return NULL;
16910 }
16911
16912 #ifndef _amigados
16913 int
16914 StrCaseCmp (char *s1, char *s2)
16915 {
16916     char c1, c2;
16917
16918     for (;;) {
16919         c1 = ToLower(*s1++);
16920         c2 = ToLower(*s2++);
16921         if (c1 > c2) return 1;
16922         if (c1 < c2) return -1;
16923         if (c1 == NULLCHAR) return 0;
16924     }
16925 }
16926
16927
16928 int
16929 ToLower (int c)
16930 {
16931     return isupper(c) ? tolower(c) : c;
16932 }
16933
16934
16935 int
16936 ToUpper (int c)
16937 {
16938     return islower(c) ? toupper(c) : c;
16939 }
16940 #endif /* !_amigados    */
16941
16942 char *
16943 StrSave (char *s)
16944 {
16945   char *ret;
16946
16947   if ((ret = (char *) malloc(strlen(s) + 1)))
16948     {
16949       safeStrCpy(ret, s, strlen(s)+1);
16950     }
16951   return ret;
16952 }
16953
16954 char *
16955 StrSavePtr (char *s, char **savePtr)
16956 {
16957     if (*savePtr) {
16958         free(*savePtr);
16959     }
16960     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16961       safeStrCpy(*savePtr, s, strlen(s)+1);
16962     }
16963     return(*savePtr);
16964 }
16965
16966 char *
16967 PGNDate ()
16968 {
16969     time_t clock;
16970     struct tm *tm;
16971     char buf[MSG_SIZ];
16972
16973     clock = time((time_t *)NULL);
16974     tm = localtime(&clock);
16975     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16976             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16977     return StrSave(buf);
16978 }
16979
16980
16981 char *
16982 PositionToFEN (int move, char *overrideCastling)
16983 {
16984     int i, j, fromX, fromY, toX, toY;
16985     int whiteToPlay;
16986     char buf[MSG_SIZ];
16987     char *p, *q;
16988     int emptycount;
16989     ChessSquare piece;
16990
16991     whiteToPlay = (gameMode == EditPosition) ?
16992       !blackPlaysFirst : (move % 2 == 0);
16993     p = buf;
16994
16995     /* Piece placement data */
16996     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16997         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16998         emptycount = 0;
16999         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17000             if (boards[move][i][j] == EmptySquare) {
17001                 emptycount++;
17002             } else { ChessSquare piece = boards[move][i][j];
17003                 if (emptycount > 0) {
17004                     if(emptycount<10) /* [HGM] can be >= 10 */
17005                         *p++ = '0' + emptycount;
17006                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17007                     emptycount = 0;
17008                 }
17009                 if(PieceToChar(piece) == '+') {
17010                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17011                     *p++ = '+';
17012                     piece = (ChessSquare)(DEMOTED piece);
17013                 }
17014                 *p++ = PieceToChar(piece);
17015                 if(p[-1] == '~') {
17016                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17017                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17018                     *p++ = '~';
17019                 }
17020             }
17021         }
17022         if (emptycount > 0) {
17023             if(emptycount<10) /* [HGM] can be >= 10 */
17024                 *p++ = '0' + emptycount;
17025             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17026             emptycount = 0;
17027         }
17028         *p++ = '/';
17029     }
17030     *(p - 1) = ' ';
17031
17032     /* [HGM] print Crazyhouse or Shogi holdings */
17033     if( gameInfo.holdingsWidth ) {
17034         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17035         q = p;
17036         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17037             piece = boards[move][i][BOARD_WIDTH-1];
17038             if( piece != EmptySquare )
17039               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17040                   *p++ = PieceToChar(piece);
17041         }
17042         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17043             piece = boards[move][BOARD_HEIGHT-i-1][0];
17044             if( piece != EmptySquare )
17045               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17046                   *p++ = PieceToChar(piece);
17047         }
17048
17049         if( q == p ) *p++ = '-';
17050         *p++ = ']';
17051         *p++ = ' ';
17052     }
17053
17054     /* Active color */
17055     *p++ = whiteToPlay ? 'w' : 'b';
17056     *p++ = ' ';
17057
17058   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17059     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17060   } else {
17061   if(nrCastlingRights) {
17062      q = p;
17063      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17064        /* [HGM] write directly from rights */
17065            if(boards[move][CASTLING][2] != NoRights &&
17066               boards[move][CASTLING][0] != NoRights   )
17067                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17068            if(boards[move][CASTLING][2] != NoRights &&
17069               boards[move][CASTLING][1] != NoRights   )
17070                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17071            if(boards[move][CASTLING][5] != NoRights &&
17072               boards[move][CASTLING][3] != NoRights   )
17073                 *p++ = boards[move][CASTLING][3] + AAA;
17074            if(boards[move][CASTLING][5] != NoRights &&
17075               boards[move][CASTLING][4] != NoRights   )
17076                 *p++ = boards[move][CASTLING][4] + AAA;
17077      } else {
17078
17079         /* [HGM] write true castling rights */
17080         if( nrCastlingRights == 6 ) {
17081             int q, k=0;
17082             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17083                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17084             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17085                  boards[move][CASTLING][2] != NoRights  );
17086             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17087                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17088                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17089                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17090                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17091             }
17092             if(q) *p++ = 'Q';
17093             k = 0;
17094             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17095                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17096             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17097                  boards[move][CASTLING][5] != NoRights  );
17098             if(gameInfo.variant == VariantSChess) {
17099                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17100                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17101                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17102                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17103             }
17104             if(q) *p++ = 'q';
17105         }
17106      }
17107      if (q == p) *p++ = '-'; /* No castling rights */
17108      *p++ = ' ';
17109   }
17110
17111   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17112      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17113     /* En passant target square */
17114     if (move > backwardMostMove) {
17115         fromX = moveList[move - 1][0] - AAA;
17116         fromY = moveList[move - 1][1] - ONE;
17117         toX = moveList[move - 1][2] - AAA;
17118         toY = moveList[move - 1][3] - ONE;
17119         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17120             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17121             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17122             fromX == toX) {
17123             /* 2-square pawn move just happened */
17124             *p++ = toX + AAA;
17125             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17126         } else {
17127             *p++ = '-';
17128         }
17129     } else if(move == backwardMostMove) {
17130         // [HGM] perhaps we should always do it like this, and forget the above?
17131         if((signed char)boards[move][EP_STATUS] >= 0) {
17132             *p++ = boards[move][EP_STATUS] + AAA;
17133             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17134         } else {
17135             *p++ = '-';
17136         }
17137     } else {
17138         *p++ = '-';
17139     }
17140     *p++ = ' ';
17141   }
17142   }
17143
17144     /* [HGM] find reversible plies */
17145     {   int i = 0, j=move;
17146
17147         if (appData.debugMode) { int k;
17148             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17149             for(k=backwardMostMove; k<=forwardMostMove; k++)
17150                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17151
17152         }
17153
17154         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17155         if( j == backwardMostMove ) i += initialRulePlies;
17156         sprintf(p, "%d ", i);
17157         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17158     }
17159     /* Fullmove number */
17160     sprintf(p, "%d", (move / 2) + 1);
17161
17162     return StrSave(buf);
17163 }
17164
17165 Boolean
17166 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17167 {
17168     int i, j;
17169     char *p, c;
17170     int emptycount, virgin[BOARD_FILES];
17171     ChessSquare piece;
17172
17173     p = fen;
17174
17175     /* [HGM] by default clear Crazyhouse holdings, if present */
17176     if(gameInfo.holdingsWidth) {
17177        for(i=0; i<BOARD_HEIGHT; i++) {
17178            board[i][0]             = EmptySquare; /* black holdings */
17179            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17180            board[i][1]             = (ChessSquare) 0; /* black counts */
17181            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17182        }
17183     }
17184
17185     /* Piece placement data */
17186     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17187         j = 0;
17188         for (;;) {
17189             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17190                 if (*p == '/') p++;
17191                 emptycount = gameInfo.boardWidth - j;
17192                 while (emptycount--)
17193                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17194                 break;
17195 #if(BOARD_FILES >= 10)
17196             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17197                 p++; emptycount=10;
17198                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17199                 while (emptycount--)
17200                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17201 #endif
17202             } else if (isdigit(*p)) {
17203                 emptycount = *p++ - '0';
17204                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17205                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17206                 while (emptycount--)
17207                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17208             } else if (*p == '+' || isalpha(*p)) {
17209                 if (j >= gameInfo.boardWidth) return FALSE;
17210                 if(*p=='+') {
17211                     piece = CharToPiece(*++p);
17212                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17213                     piece = (ChessSquare) (PROMOTED piece ); p++;
17214                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17215                 } else piece = CharToPiece(*p++);
17216
17217                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17218                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17219                     piece = (ChessSquare) (PROMOTED piece);
17220                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17221                     p++;
17222                 }
17223                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17224             } else {
17225                 return FALSE;
17226             }
17227         }
17228     }
17229     while (*p == '/' || *p == ' ') p++;
17230
17231     /* [HGM] look for Crazyhouse holdings here */
17232     while(*p==' ') p++;
17233     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17234         if(*p == '[') p++;
17235         if(*p == '-' ) p++; /* empty holdings */ else {
17236             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17237             /* if we would allow FEN reading to set board size, we would   */
17238             /* have to add holdings and shift the board read so far here   */
17239             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17240                 p++;
17241                 if((int) piece >= (int) BlackPawn ) {
17242                     i = (int)piece - (int)BlackPawn;
17243                     i = PieceToNumber((ChessSquare)i);
17244                     if( i >= gameInfo.holdingsSize ) return FALSE;
17245                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17246                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17247                 } else {
17248                     i = (int)piece - (int)WhitePawn;
17249                     i = PieceToNumber((ChessSquare)i);
17250                     if( i >= gameInfo.holdingsSize ) return FALSE;
17251                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17252                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17253                 }
17254             }
17255         }
17256         if(*p == ']') p++;
17257     }
17258
17259     while(*p == ' ') p++;
17260
17261     /* Active color */
17262     c = *p++;
17263     if(appData.colorNickNames) {
17264       if( c == appData.colorNickNames[0] ) c = 'w'; else
17265       if( c == appData.colorNickNames[1] ) c = 'b';
17266     }
17267     switch (c) {
17268       case 'w':
17269         *blackPlaysFirst = FALSE;
17270         break;
17271       case 'b':
17272         *blackPlaysFirst = TRUE;
17273         break;
17274       default:
17275         return FALSE;
17276     }
17277
17278     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17279     /* return the extra info in global variiables             */
17280
17281     /* set defaults in case FEN is incomplete */
17282     board[EP_STATUS] = EP_UNKNOWN;
17283     for(i=0; i<nrCastlingRights; i++ ) {
17284         board[CASTLING][i] =
17285             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17286     }   /* assume possible unless obviously impossible */
17287     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17288     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17289     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17290                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17291     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17292     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17293     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17294                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17295     FENrulePlies = 0;
17296
17297     while(*p==' ') p++;
17298     if(nrCastlingRights) {
17299       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17300       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17301           /* castling indicator present, so default becomes no castlings */
17302           for(i=0; i<nrCastlingRights; i++ ) {
17303                  board[CASTLING][i] = NoRights;
17304           }
17305       }
17306       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17307              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17308              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17309              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17310         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17311
17312         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17313             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17314             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17315         }
17316         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17317             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17318         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17319                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17320         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17321                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17322         switch(c) {
17323           case'K':
17324               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17325               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17326               board[CASTLING][2] = whiteKingFile;
17327               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17328               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17329               break;
17330           case'Q':
17331               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17332               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17333               board[CASTLING][2] = whiteKingFile;
17334               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17335               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17336               break;
17337           case'k':
17338               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17339               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17340               board[CASTLING][5] = blackKingFile;
17341               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17342               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17343               break;
17344           case'q':
17345               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17346               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17347               board[CASTLING][5] = blackKingFile;
17348               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17349               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17350           case '-':
17351               break;
17352           default: /* FRC castlings */
17353               if(c >= 'a') { /* black rights */
17354                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17355                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17356                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17357                   if(i == BOARD_RGHT) break;
17358                   board[CASTLING][5] = i;
17359                   c -= AAA;
17360                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17361                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17362                   if(c > i)
17363                       board[CASTLING][3] = c;
17364                   else
17365                       board[CASTLING][4] = c;
17366               } else { /* white rights */
17367                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17368                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17369                     if(board[0][i] == WhiteKing) break;
17370                   if(i == BOARD_RGHT) break;
17371                   board[CASTLING][2] = i;
17372                   c -= AAA - 'a' + 'A';
17373                   if(board[0][c] >= WhiteKing) break;
17374                   if(c > i)
17375                       board[CASTLING][0] = c;
17376                   else
17377                       board[CASTLING][1] = c;
17378               }
17379         }
17380       }
17381       for(i=0; i<nrCastlingRights; i++)
17382         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17383       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17384     if (appData.debugMode) {
17385         fprintf(debugFP, "FEN castling rights:");
17386         for(i=0; i<nrCastlingRights; i++)
17387         fprintf(debugFP, " %d", board[CASTLING][i]);
17388         fprintf(debugFP, "\n");
17389     }
17390
17391       while(*p==' ') p++;
17392     }
17393
17394     /* read e.p. field in games that know e.p. capture */
17395     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17396        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17397       if(*p=='-') {
17398         p++; board[EP_STATUS] = EP_NONE;
17399       } else {
17400          char c = *p++ - AAA;
17401
17402          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17403          if(*p >= '0' && *p <='9') p++;
17404          board[EP_STATUS] = c;
17405       }
17406     }
17407
17408
17409     if(sscanf(p, "%d", &i) == 1) {
17410         FENrulePlies = i; /* 50-move ply counter */
17411         /* (The move number is still ignored)    */
17412     }
17413
17414     return TRUE;
17415 }
17416
17417 void
17418 EditPositionPasteFEN (char *fen)
17419 {
17420   if (fen != NULL) {
17421     Board initial_position;
17422
17423     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17424       DisplayError(_("Bad FEN position in clipboard"), 0);
17425       return ;
17426     } else {
17427       int savedBlackPlaysFirst = blackPlaysFirst;
17428       EditPositionEvent();
17429       blackPlaysFirst = savedBlackPlaysFirst;
17430       CopyBoard(boards[0], initial_position);
17431       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17432       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17433       DisplayBothClocks();
17434       DrawPosition(FALSE, boards[currentMove]);
17435     }
17436   }
17437 }
17438
17439 static char cseq[12] = "\\   ";
17440
17441 Boolean
17442 set_cont_sequence (char *new_seq)
17443 {
17444     int len;
17445     Boolean ret;
17446
17447     // handle bad attempts to set the sequence
17448         if (!new_seq)
17449                 return 0; // acceptable error - no debug
17450
17451     len = strlen(new_seq);
17452     ret = (len > 0) && (len < sizeof(cseq));
17453     if (ret)
17454       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17455     else if (appData.debugMode)
17456       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17457     return ret;
17458 }
17459
17460 /*
17461     reformat a source message so words don't cross the width boundary.  internal
17462     newlines are not removed.  returns the wrapped size (no null character unless
17463     included in source message).  If dest is NULL, only calculate the size required
17464     for the dest buffer.  lp argument indicats line position upon entry, and it's
17465     passed back upon exit.
17466 */
17467 int
17468 wrap (char *dest, char *src, int count, int width, int *lp)
17469 {
17470     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17471
17472     cseq_len = strlen(cseq);
17473     old_line = line = *lp;
17474     ansi = len = clen = 0;
17475
17476     for (i=0; i < count; i++)
17477     {
17478         if (src[i] == '\033')
17479             ansi = 1;
17480
17481         // if we hit the width, back up
17482         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17483         {
17484             // store i & len in case the word is too long
17485             old_i = i, old_len = len;
17486
17487             // find the end of the last word
17488             while (i && src[i] != ' ' && src[i] != '\n')
17489             {
17490                 i--;
17491                 len--;
17492             }
17493
17494             // word too long?  restore i & len before splitting it
17495             if ((old_i-i+clen) >= width)
17496             {
17497                 i = old_i;
17498                 len = old_len;
17499             }
17500
17501             // extra space?
17502             if (i && src[i-1] == ' ')
17503                 len--;
17504
17505             if (src[i] != ' ' && src[i] != '\n')
17506             {
17507                 i--;
17508                 if (len)
17509                     len--;
17510             }
17511
17512             // now append the newline and continuation sequence
17513             if (dest)
17514                 dest[len] = '\n';
17515             len++;
17516             if (dest)
17517                 strncpy(dest+len, cseq, cseq_len);
17518             len += cseq_len;
17519             line = cseq_len;
17520             clen = cseq_len;
17521             continue;
17522         }
17523
17524         if (dest)
17525             dest[len] = src[i];
17526         len++;
17527         if (!ansi)
17528             line++;
17529         if (src[i] == '\n')
17530             line = 0;
17531         if (src[i] == 'm')
17532             ansi = 0;
17533     }
17534     if (dest && appData.debugMode)
17535     {
17536         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17537             count, width, line, len, *lp);
17538         show_bytes(debugFP, src, count);
17539         fprintf(debugFP, "\ndest: ");
17540         show_bytes(debugFP, dest, len);
17541         fprintf(debugFP, "\n");
17542     }
17543     *lp = dest ? line : old_line;
17544
17545     return len;
17546 }
17547
17548 // [HGM] vari: routines for shelving variations
17549 Boolean modeRestore = FALSE;
17550
17551 void
17552 PushInner (int firstMove, int lastMove)
17553 {
17554         int i, j, nrMoves = lastMove - firstMove;
17555
17556         // push current tail of game on stack
17557         savedResult[storedGames] = gameInfo.result;
17558         savedDetails[storedGames] = gameInfo.resultDetails;
17559         gameInfo.resultDetails = NULL;
17560         savedFirst[storedGames] = firstMove;
17561         savedLast [storedGames] = lastMove;
17562         savedFramePtr[storedGames] = framePtr;
17563         framePtr -= nrMoves; // reserve space for the boards
17564         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17565             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17566             for(j=0; j<MOVE_LEN; j++)
17567                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17568             for(j=0; j<2*MOVE_LEN; j++)
17569                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17570             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17571             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17572             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17573             pvInfoList[firstMove+i-1].depth = 0;
17574             commentList[framePtr+i] = commentList[firstMove+i];
17575             commentList[firstMove+i] = NULL;
17576         }
17577
17578         storedGames++;
17579         forwardMostMove = firstMove; // truncate game so we can start variation
17580 }
17581
17582 void
17583 PushTail (int firstMove, int lastMove)
17584 {
17585         if(appData.icsActive) { // only in local mode
17586                 forwardMostMove = currentMove; // mimic old ICS behavior
17587                 return;
17588         }
17589         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17590
17591         PushInner(firstMove, lastMove);
17592         if(storedGames == 1) GreyRevert(FALSE);
17593         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17594 }
17595
17596 void
17597 PopInner (Boolean annotate)
17598 {
17599         int i, j, nrMoves;
17600         char buf[8000], moveBuf[20];
17601
17602         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17603         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17604         nrMoves = savedLast[storedGames] - currentMove;
17605         if(annotate) {
17606                 int cnt = 10;
17607                 if(!WhiteOnMove(currentMove))
17608                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17609                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17610                 for(i=currentMove; i<forwardMostMove; i++) {
17611                         if(WhiteOnMove(i))
17612                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17613                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17614                         strcat(buf, moveBuf);
17615                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17616                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17617                 }
17618                 strcat(buf, ")");
17619         }
17620         for(i=1; i<=nrMoves; i++) { // copy last variation back
17621             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17622             for(j=0; j<MOVE_LEN; j++)
17623                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17624             for(j=0; j<2*MOVE_LEN; j++)
17625                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17626             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17627             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17628             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17629             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17630             commentList[currentMove+i] = commentList[framePtr+i];
17631             commentList[framePtr+i] = NULL;
17632         }
17633         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17634         framePtr = savedFramePtr[storedGames];
17635         gameInfo.result = savedResult[storedGames];
17636         if(gameInfo.resultDetails != NULL) {
17637             free(gameInfo.resultDetails);
17638       }
17639         gameInfo.resultDetails = savedDetails[storedGames];
17640         forwardMostMove = currentMove + nrMoves;
17641 }
17642
17643 Boolean
17644 PopTail (Boolean annotate)
17645 {
17646         if(appData.icsActive) return FALSE; // only in local mode
17647         if(!storedGames) return FALSE; // sanity
17648         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17649
17650         PopInner(annotate);
17651         if(currentMove < forwardMostMove) ForwardEvent(); else
17652         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17653
17654         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17655         return TRUE;
17656 }
17657
17658 void
17659 CleanupTail ()
17660 {       // remove all shelved variations
17661         int i;
17662         for(i=0; i<storedGames; i++) {
17663             if(savedDetails[i])
17664                 free(savedDetails[i]);
17665             savedDetails[i] = NULL;
17666         }
17667         for(i=framePtr; i<MAX_MOVES; i++) {
17668                 if(commentList[i]) free(commentList[i]);
17669                 commentList[i] = NULL;
17670         }
17671         framePtr = MAX_MOVES-1;
17672         storedGames = 0;
17673 }
17674
17675 void
17676 LoadVariation (int index, char *text)
17677 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17678         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17679         int level = 0, move;
17680
17681         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17682         // first find outermost bracketing variation
17683         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17684             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17685                 if(*p == '{') wait = '}'; else
17686                 if(*p == '[') wait = ']'; else
17687                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17688                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17689             }
17690             if(*p == wait) wait = NULLCHAR; // closing ]} found
17691             p++;
17692         }
17693         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17694         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17695         end[1] = NULLCHAR; // clip off comment beyond variation
17696         ToNrEvent(currentMove-1);
17697         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17698         // kludge: use ParsePV() to append variation to game
17699         move = currentMove;
17700         ParsePV(start, TRUE, TRUE);
17701         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17702         ClearPremoveHighlights();
17703         CommentPopDown();
17704         ToNrEvent(currentMove+1);
17705 }
17706
17707 void
17708 LoadTheme ()
17709 {
17710     char *p, *q, buf[MSG_SIZ];
17711     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17712         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17713         ParseArgsFromString(buf);
17714         ActivateTheme(TRUE); // also redo colors
17715         return;
17716     }
17717     p = nickName;
17718     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17719     {
17720         int len;
17721         q = appData.themeNames;
17722         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17723       if(appData.useBitmaps) {
17724         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17725                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17726                 appData.liteBackTextureMode,
17727                 appData.darkBackTextureMode );
17728       } else {
17729         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17730                 Col2Text(2),   // lightSquareColor
17731                 Col2Text(3) ); // darkSquareColor
17732       }
17733       if(appData.useBorder) {
17734         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17735                 appData.border);
17736       } else {
17737         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17738       }
17739       if(appData.useFont) {
17740         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17741                 appData.renderPiecesWithFont,
17742                 appData.fontToPieceTable,
17743                 Col2Text(9),    // appData.fontBackColorWhite
17744                 Col2Text(10) ); // appData.fontForeColorBlack
17745       } else {
17746         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17747                 appData.pieceDirectory);
17748         if(!appData.pieceDirectory[0])
17749           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17750                 Col2Text(0),   // whitePieceColor
17751                 Col2Text(1) ); // blackPieceColor
17752       }
17753       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17754                 Col2Text(4),   // highlightSquareColor
17755                 Col2Text(5) ); // premoveHighlightColor
17756         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17757         if(insert != q) insert[-1] = NULLCHAR;
17758         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17759         if(q)   free(q);
17760     }
17761     ActivateTheme(FALSE);
17762 }