Increase number of piece types to 44
[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 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
576 };
577
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
590 };
591
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
597 };
598
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
604 };
605
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
611 };
612
613 #ifdef GOTHIC
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !GOTHIC
621 #define GothicArray CapablancaArray
622 #endif // !GOTHIC
623
624 #ifdef FALCON
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
630 };
631 #else // !FALCON
632 #define FalconArray CapablancaArray
633 #endif // !FALCON
634
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
641
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
648 };
649 ChessSquare ChuArray[6][BOARD_FILES] = {
650     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
651       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
652     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
653       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
654     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
655       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
656     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
657       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
658     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
659       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
660     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
661       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
662 };
663 #else // !(BOARD_FILES>=12)
664 #define CourierArray CapablancaArray
665 #define ChuArray CapablancaArray
666 #endif // !(BOARD_FILES>=12)
667
668
669 Board initialPosition;
670
671
672 /* Convert str to a rating. Checks for special cases of "----",
673
674    "++++", etc. Also strips ()'s */
675 int
676 string_to_rating (char *str)
677 {
678   while(*str && !isdigit(*str)) ++str;
679   if (!*str)
680     return 0;   /* One of the special "no rating" cases */
681   else
682     return atoi(str);
683 }
684
685 void
686 ClearProgramStats ()
687 {
688     /* Init programStats */
689     programStats.movelist[0] = 0;
690     programStats.depth = 0;
691     programStats.nr_moves = 0;
692     programStats.moves_left = 0;
693     programStats.nodes = 0;
694     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
695     programStats.score = 0;
696     programStats.got_only_move = 0;
697     programStats.got_fail = 0;
698     programStats.line_is_book = 0;
699 }
700
701 void
702 CommonEngineInit ()
703 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711
712     first.other = &second;
713     second.other = &first;
714
715     { float norm = 1;
716         if(appData.timeOddsMode) {
717             norm = appData.timeOdds[0];
718             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
719         }
720         first.timeOdds  = appData.timeOdds[0]/norm;
721         second.timeOdds = appData.timeOdds[1]/norm;
722     }
723
724     if(programVersion) free(programVersion);
725     if (appData.noChessProgram) {
726         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
727         sprintf(programVersion, "%s", PACKAGE_STRING);
728     } else {
729       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
730       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
731       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
732     }
733 }
734
735 void
736 UnloadEngine (ChessProgramState *cps)
737 {
738         /* Kill off first chess program */
739         if (cps->isr != NULL)
740           RemoveInputSource(cps->isr);
741         cps->isr = NULL;
742
743         if (cps->pr != NoProc) {
744             ExitAnalyzeMode();
745             DoSleep( appData.delayBeforeQuit );
746             SendToProgram("quit\n", cps);
747             DoSleep( appData.delayAfterQuit );
748             DestroyChildProcess(cps->pr, cps->useSigterm);
749         }
750         cps->pr = NoProc;
751         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
752 }
753
754 void
755 ClearOptions (ChessProgramState *cps)
756 {
757     int i;
758     cps->nrOptions = cps->comboCnt = 0;
759     for(i=0; i<MAX_OPTIONS; i++) {
760         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
761         cps->option[i].textValue = 0;
762     }
763 }
764
765 char *engineNames[] = {
766   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
767      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
768 N_("first"),
769   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
770      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
771 N_("second")
772 };
773
774 void
775 InitEngine (ChessProgramState *cps, int n)
776 {   // [HGM] all engine initialiation put in a function that does one engine
777
778     ClearOptions(cps);
779
780     cps->which = engineNames[n];
781     cps->maybeThinking = FALSE;
782     cps->pr = NoProc;
783     cps->isr = NULL;
784     cps->sendTime = 2;
785     cps->sendDrawOffers = 1;
786
787     cps->program = appData.chessProgram[n];
788     cps->host = appData.host[n];
789     cps->dir = appData.directory[n];
790     cps->initString = appData.engInitString[n];
791     cps->computerString = appData.computerString[n];
792     cps->useSigint  = TRUE;
793     cps->useSigterm = TRUE;
794     cps->reuse = appData.reuse[n];
795     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
796     cps->useSetboard = FALSE;
797     cps->useSAN = FALSE;
798     cps->usePing = FALSE;
799     cps->lastPing = 0;
800     cps->lastPong = 0;
801     cps->usePlayother = FALSE;
802     cps->useColors = TRUE;
803     cps->useUsermove = FALSE;
804     cps->sendICS = FALSE;
805     cps->sendName = appData.icsActive;
806     cps->sdKludge = FALSE;
807     cps->stKludge = FALSE;
808     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
809     TidyProgramName(cps->program, cps->host, cps->tidy);
810     cps->matchWins = 0;
811     ASSIGN(cps->variants, appData.variant);
812     cps->analysisSupport = 2; /* detect */
813     cps->analyzing = FALSE;
814     cps->initDone = FALSE;
815     cps->reload = FALSE;
816
817     /* New features added by Tord: */
818     cps->useFEN960 = FALSE;
819     cps->useOOCastle = TRUE;
820     /* End of new features added by Tord. */
821     cps->fenOverride  = appData.fenOverride[n];
822
823     /* [HGM] time odds: set factor for each machine */
824     cps->timeOdds  = appData.timeOdds[n];
825
826     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
827     cps->accumulateTC = appData.accumulateTC[n];
828     cps->maxNrOfSessions = 1;
829
830     /* [HGM] debug */
831     cps->debug = FALSE;
832
833     cps->supportsNPS = UNKNOWN;
834     cps->memSize = FALSE;
835     cps->maxCores = FALSE;
836     ASSIGN(cps->egtFormats, "");
837
838     /* [HGM] options */
839     cps->optionSettings  = appData.engOptions[n];
840
841     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
842     cps->isUCI = appData.isUCI[n]; /* [AS] */
843     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
844     cps->highlight = 0;
845
846     if (appData.protocolVersion[n] > PROTOVER
847         || appData.protocolVersion[n] < 1)
848       {
849         char buf[MSG_SIZ];
850         int len;
851
852         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853                        appData.protocolVersion[n]);
854         if( (len >= MSG_SIZ) && appData.debugMode )
855           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
856
857         DisplayFatalError(buf, 0, 2);
858       }
859     else
860       {
861         cps->protocolVersion = appData.protocolVersion[n];
862       }
863
864     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
865     ParseFeatures(appData.featureDefaults, cps);
866 }
867
868 ChessProgramState *savCps;
869
870 GameMode oldMode;
871
872 void
873 LoadEngine ()
874 {
875     int i;
876     if(WaitForEngine(savCps, LoadEngine)) return;
877     CommonEngineInit(); // recalculate time odds
878     if(gameInfo.variant != StringToVariant(appData.variant)) {
879         // we changed variant when loading the engine; this forces us to reset
880         Reset(TRUE, savCps != &first);
881         oldMode = BeginningOfGame; // to prevent restoring old mode
882     }
883     InitChessProgram(savCps, FALSE);
884     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
885     DisplayMessage("", "");
886     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
887     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
888     ThawUI();
889     SetGNUMode();
890     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
891 }
892
893 void
894 ReplaceEngine (ChessProgramState *cps, int n)
895 {
896     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
897     keepInfo = 1;
898     if(oldMode != BeginningOfGame) EditGameEvent();
899     keepInfo = 0;
900     UnloadEngine(cps);
901     appData.noChessProgram = FALSE;
902     appData.clockMode = TRUE;
903     InitEngine(cps, n);
904     UpdateLogos(TRUE);
905     if(n) return; // only startup first engine immediately; second can wait
906     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
907     LoadEngine();
908 }
909
910 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
911 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
912
913 static char resetOptions[] =
914         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
915         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
916         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
917         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
918
919 void
920 FloatToFront(char **list, char *engineLine)
921 {
922     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
923     int i=0;
924     if(appData.recentEngines <= 0) return;
925     TidyProgramName(engineLine, "localhost", tidy+1);
926     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
927     strncpy(buf+1, *list, MSG_SIZ-50);
928     if(p = strstr(buf, tidy)) { // tidy name appears in list
929         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
930         while(*p++ = *++q); // squeeze out
931     }
932     strcat(tidy, buf+1); // put list behind tidy name
933     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
934     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
935     ASSIGN(*list, tidy+1);
936 }
937
938 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
939
940 void
941 Load (ChessProgramState *cps, int i)
942 {
943     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
944     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
945         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
946         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
947         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
948         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
949         appData.firstProtocolVersion = PROTOVER;
950         ParseArgsFromString(buf);
951         SwapEngines(i);
952         ReplaceEngine(cps, i);
953         FloatToFront(&appData.recentEngineList, engineLine);
954         return;
955     }
956     p = engineName;
957     while(q = strchr(p, SLASH)) p = q+1;
958     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
959     if(engineDir[0] != NULLCHAR) {
960         ASSIGN(appData.directory[i], engineDir); p = engineName;
961     } else if(p != engineName) { // derive directory from engine path, when not given
962         p[-1] = 0;
963         ASSIGN(appData.directory[i], engineName);
964         p[-1] = SLASH;
965         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
966     } else { ASSIGN(appData.directory[i], "."); }
967     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
968     if(params[0]) {
969         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
970         snprintf(command, MSG_SIZ, "%s %s", p, params);
971         p = command;
972     }
973     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
974     ASSIGN(appData.chessProgram[i], p);
975     appData.isUCI[i] = isUCI;
976     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
977     appData.hasOwnBookUCI[i] = hasBook;
978     if(!nickName[0]) useNick = FALSE;
979     if(useNick) ASSIGN(appData.pgnName[i], nickName);
980     if(addToList) {
981         int len;
982         char quote;
983         q = firstChessProgramNames;
984         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
985         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
986         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
987                         quote, p, quote, appData.directory[i],
988                         useNick ? " -fn \"" : "",
989                         useNick ? nickName : "",
990                         useNick ? "\"" : "",
991                         v1 ? " -firstProtocolVersion 1" : "",
992                         hasBook ? "" : " -fNoOwnBookUCI",
993                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
994                         storeVariant ? " -variant " : "",
995                         storeVariant ? VariantName(gameInfo.variant) : "");
996         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
997         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
998         if(insert != q) insert[-1] = NULLCHAR;
999         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1000         if(q)   free(q);
1001         FloatToFront(&appData.recentEngineList, buf);
1002     }
1003     ReplaceEngine(cps, i);
1004 }
1005
1006 void
1007 InitTimeControls ()
1008 {
1009     int matched, min, sec;
1010     /*
1011      * Parse timeControl resource
1012      */
1013     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1014                           appData.movesPerSession)) {
1015         char buf[MSG_SIZ];
1016         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1017         DisplayFatalError(buf, 0, 2);
1018     }
1019
1020     /*
1021      * Parse searchTime resource
1022      */
1023     if (*appData.searchTime != NULLCHAR) {
1024         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1025         if (matched == 1) {
1026             searchTime = min * 60;
1027         } else if (matched == 2) {
1028             searchTime = min * 60 + sec;
1029         } else {
1030             char buf[MSG_SIZ];
1031             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1032             DisplayFatalError(buf, 0, 2);
1033         }
1034     }
1035 }
1036
1037 void
1038 InitBackEnd1 ()
1039 {
1040
1041     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1042     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1043
1044     GetTimeMark(&programStartTime);
1045     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1046     appData.seedBase = random() + (random()<<15);
1047     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1048
1049     ClearProgramStats();
1050     programStats.ok_to_send = 1;
1051     programStats.seen_stat = 0;
1052
1053     /*
1054      * Initialize game list
1055      */
1056     ListNew(&gameList);
1057
1058
1059     /*
1060      * Internet chess server status
1061      */
1062     if (appData.icsActive) {
1063         appData.matchMode = FALSE;
1064         appData.matchGames = 0;
1065 #if ZIPPY
1066         appData.noChessProgram = !appData.zippyPlay;
1067 #else
1068         appData.zippyPlay = FALSE;
1069         appData.zippyTalk = FALSE;
1070         appData.noChessProgram = TRUE;
1071 #endif
1072         if (*appData.icsHelper != NULLCHAR) {
1073             appData.useTelnet = TRUE;
1074             appData.telnetProgram = appData.icsHelper;
1075         }
1076     } else {
1077         appData.zippyTalk = appData.zippyPlay = FALSE;
1078     }
1079
1080     /* [AS] Initialize pv info list [HGM] and game state */
1081     {
1082         int i, j;
1083
1084         for( i=0; i<=framePtr; i++ ) {
1085             pvInfoList[i].depth = -1;
1086             boards[i][EP_STATUS] = EP_NONE;
1087             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1088         }
1089     }
1090
1091     InitTimeControls();
1092
1093     /* [AS] Adjudication threshold */
1094     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1095
1096     InitEngine(&first, 0);
1097     InitEngine(&second, 1);
1098     CommonEngineInit();
1099
1100     pairing.which = "pairing"; // pairing engine
1101     pairing.pr = NoProc;
1102     pairing.isr = NULL;
1103     pairing.program = appData.pairingEngine;
1104     pairing.host = "localhost";
1105     pairing.dir = ".";
1106
1107     if (appData.icsActive) {
1108         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1109     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1110         appData.clockMode = FALSE;
1111         first.sendTime = second.sendTime = 0;
1112     }
1113
1114 #if ZIPPY
1115     /* Override some settings from environment variables, for backward
1116        compatibility.  Unfortunately it's not feasible to have the env
1117        vars just set defaults, at least in xboard.  Ugh.
1118     */
1119     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1120       ZippyInit();
1121     }
1122 #endif
1123
1124     if (!appData.icsActive) {
1125       char buf[MSG_SIZ];
1126       int len;
1127
1128       /* Check for variants that are supported only in ICS mode,
1129          or not at all.  Some that are accepted here nevertheless
1130          have bugs; see comments below.
1131       */
1132       VariantClass variant = StringToVariant(appData.variant);
1133       switch (variant) {
1134       case VariantBughouse:     /* need four players and two boards */
1135       case VariantKriegspiel:   /* need to hide pieces and move details */
1136         /* case VariantFischeRandom: (Fabien: moved below) */
1137         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1138         if( (len >= MSG_SIZ) && appData.debugMode )
1139           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1140
1141         DisplayFatalError(buf, 0, 2);
1142         return;
1143
1144       case VariantUnknown:
1145       case VariantLoadable:
1146       case Variant29:
1147       case Variant30:
1148       case Variant31:
1149       case Variant32:
1150       case Variant33:
1151       case Variant34:
1152       case Variant35:
1153       case Variant36:
1154       default:
1155         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1156         if( (len >= MSG_SIZ) && appData.debugMode )
1157           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1158
1159         DisplayFatalError(buf, 0, 2);
1160         return;
1161
1162       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1163       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1164       case VariantGothic:     /* [HGM] should work */
1165       case VariantCapablanca: /* [HGM] should work */
1166       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1167       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1168       case VariantChu:        /* [HGM] experimental */
1169       case VariantKnightmate: /* [HGM] should work */
1170       case VariantCylinder:   /* [HGM] untested */
1171       case VariantFalcon:     /* [HGM] untested */
1172       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1173                                  offboard interposition not understood */
1174       case VariantNormal:     /* definitely works! */
1175       case VariantWildCastle: /* pieces not automatically shuffled */
1176       case VariantNoCastle:   /* pieces not automatically shuffled */
1177       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1178       case VariantLosers:     /* should work except for win condition,
1179                                  and doesn't know captures are mandatory */
1180       case VariantSuicide:    /* should work except for win condition,
1181                                  and doesn't know captures are mandatory */
1182       case VariantGiveaway:   /* should work except for win condition,
1183                                  and doesn't know captures are mandatory */
1184       case VariantTwoKings:   /* should work */
1185       case VariantAtomic:     /* should work except for win condition */
1186       case Variant3Check:     /* should work except for win condition */
1187       case VariantShatranj:   /* should work except for all win conditions */
1188       case VariantMakruk:     /* should work except for draw countdown */
1189       case VariantASEAN :     /* should work except for draw countdown */
1190       case VariantBerolina:   /* might work if TestLegality is off */
1191       case VariantCapaRandom: /* should work */
1192       case VariantJanus:      /* should work */
1193       case VariantSuper:      /* experimental */
1194       case VariantGreat:      /* experimental, requires legality testing to be off */
1195       case VariantSChess:     /* S-Chess, should work */
1196       case VariantGrand:      /* should work */
1197       case VariantSpartan:    /* should work */
1198         break;
1199       }
1200     }
1201
1202 }
1203
1204 int
1205 NextIntegerFromString (char ** str, long * value)
1206 {
1207     int result = -1;
1208     char * s = *str;
1209
1210     while( *s == ' ' || *s == '\t' ) {
1211         s++;
1212     }
1213
1214     *value = 0;
1215
1216     if( *s >= '0' && *s <= '9' ) {
1217         while( *s >= '0' && *s <= '9' ) {
1218             *value = *value * 10 + (*s - '0');
1219             s++;
1220         }
1221
1222         result = 0;
1223     }
1224
1225     *str = s;
1226
1227     return result;
1228 }
1229
1230 int
1231 NextTimeControlFromString (char ** str, long * value)
1232 {
1233     long temp;
1234     int result = NextIntegerFromString( str, &temp );
1235
1236     if( result == 0 ) {
1237         *value = temp * 60; /* Minutes */
1238         if( **str == ':' ) {
1239             (*str)++;
1240             result = NextIntegerFromString( str, &temp );
1241             *value += temp; /* Seconds */
1242         }
1243     }
1244
1245     return result;
1246 }
1247
1248 int
1249 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1250 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1251     int result = -1, type = 0; long temp, temp2;
1252
1253     if(**str != ':') return -1; // old params remain in force!
1254     (*str)++;
1255     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1256     if( NextIntegerFromString( str, &temp ) ) return -1;
1257     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1258
1259     if(**str != '/') {
1260         /* time only: incremental or sudden-death time control */
1261         if(**str == '+') { /* increment follows; read it */
1262             (*str)++;
1263             if(**str == '!') type = *(*str)++; // Bronstein TC
1264             if(result = NextIntegerFromString( str, &temp2)) return -1;
1265             *inc = temp2 * 1000;
1266             if(**str == '.') { // read fraction of increment
1267                 char *start = ++(*str);
1268                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1269                 temp2 *= 1000;
1270                 while(start++ < *str) temp2 /= 10;
1271                 *inc += temp2;
1272             }
1273         } else *inc = 0;
1274         *moves = 0; *tc = temp * 1000; *incType = type;
1275         return 0;
1276     }
1277
1278     (*str)++; /* classical time control */
1279     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1280
1281     if(result == 0) {
1282         *moves = temp;
1283         *tc    = temp2 * 1000;
1284         *inc   = 0;
1285         *incType = type;
1286     }
1287     return result;
1288 }
1289
1290 int
1291 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1292 {   /* [HGM] get time to add from the multi-session time-control string */
1293     int incType, moves=1; /* kludge to force reading of first session */
1294     long time, increment;
1295     char *s = tcString;
1296
1297     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1298     do {
1299         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1300         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1301         if(movenr == -1) return time;    /* last move before new session     */
1302         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1303         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1304         if(!moves) return increment;     /* current session is incremental   */
1305         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1306     } while(movenr >= -1);               /* try again for next session       */
1307
1308     return 0; // no new time quota on this move
1309 }
1310
1311 int
1312 ParseTimeControl (char *tc, float ti, int mps)
1313 {
1314   long tc1;
1315   long tc2;
1316   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1317   int min, sec=0;
1318
1319   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1320   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1321       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1322   if(ti > 0) {
1323
1324     if(mps)
1325       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1326     else
1327       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1328   } else {
1329     if(mps)
1330       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1331     else
1332       snprintf(buf, MSG_SIZ, ":%s", mytc);
1333   }
1334   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1335
1336   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1337     return FALSE;
1338   }
1339
1340   if( *tc == '/' ) {
1341     /* Parse second time control */
1342     tc++;
1343
1344     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1345       return FALSE;
1346     }
1347
1348     if( tc2 == 0 ) {
1349       return FALSE;
1350     }
1351
1352     timeControl_2 = tc2 * 1000;
1353   }
1354   else {
1355     timeControl_2 = 0;
1356   }
1357
1358   if( tc1 == 0 ) {
1359     return FALSE;
1360   }
1361
1362   timeControl = tc1 * 1000;
1363
1364   if (ti >= 0) {
1365     timeIncrement = ti * 1000;  /* convert to ms */
1366     movesPerSession = 0;
1367   } else {
1368     timeIncrement = 0;
1369     movesPerSession = mps;
1370   }
1371   return TRUE;
1372 }
1373
1374 void
1375 InitBackEnd2 ()
1376 {
1377     if (appData.debugMode) {
1378 #    ifdef __GIT_VERSION
1379       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1380 #    else
1381       fprintf(debugFP, "Version: %s\n", programVersion);
1382 #    endif
1383     }
1384     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1385
1386     set_cont_sequence(appData.wrapContSeq);
1387     if (appData.matchGames > 0) {
1388         appData.matchMode = TRUE;
1389     } else if (appData.matchMode) {
1390         appData.matchGames = 1;
1391     }
1392     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1393         appData.matchGames = appData.sameColorGames;
1394     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1395         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1396         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1397     }
1398     Reset(TRUE, FALSE);
1399     if (appData.noChessProgram || first.protocolVersion == 1) {
1400       InitBackEnd3();
1401     } else {
1402       /* kludge: allow timeout for initial "feature" commands */
1403       FreezeUI();
1404       DisplayMessage("", _("Starting chess program"));
1405       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1406     }
1407 }
1408
1409 int
1410 CalculateIndex (int index, int gameNr)
1411 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1412     int res;
1413     if(index > 0) return index; // fixed nmber
1414     if(index == 0) return 1;
1415     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1416     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1417     return res;
1418 }
1419
1420 int
1421 LoadGameOrPosition (int gameNr)
1422 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1423     if (*appData.loadGameFile != NULLCHAR) {
1424         if (!LoadGameFromFile(appData.loadGameFile,
1425                 CalculateIndex(appData.loadGameIndex, gameNr),
1426                               appData.loadGameFile, FALSE)) {
1427             DisplayFatalError(_("Bad game file"), 0, 1);
1428             return 0;
1429         }
1430     } else if (*appData.loadPositionFile != NULLCHAR) {
1431         if (!LoadPositionFromFile(appData.loadPositionFile,
1432                 CalculateIndex(appData.loadPositionIndex, gameNr),
1433                                   appData.loadPositionFile)) {
1434             DisplayFatalError(_("Bad position file"), 0, 1);
1435             return 0;
1436         }
1437     }
1438     return 1;
1439 }
1440
1441 void
1442 ReserveGame (int gameNr, char resChar)
1443 {
1444     FILE *tf = fopen(appData.tourneyFile, "r+");
1445     char *p, *q, c, buf[MSG_SIZ];
1446     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1447     safeStrCpy(buf, lastMsg, MSG_SIZ);
1448     DisplayMessage(_("Pick new game"), "");
1449     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1450     ParseArgsFromFile(tf);
1451     p = q = appData.results;
1452     if(appData.debugMode) {
1453       char *r = appData.participants;
1454       fprintf(debugFP, "results = '%s'\n", p);
1455       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1456       fprintf(debugFP, "\n");
1457     }
1458     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1459     nextGame = q - p;
1460     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1461     safeStrCpy(q, p, strlen(p) + 2);
1462     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1463     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1464     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1465         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1466         q[nextGame] = '*';
1467     }
1468     fseek(tf, -(strlen(p)+4), SEEK_END);
1469     c = fgetc(tf);
1470     if(c != '"') // depending on DOS or Unix line endings we can be one off
1471          fseek(tf, -(strlen(p)+2), SEEK_END);
1472     else fseek(tf, -(strlen(p)+3), SEEK_END);
1473     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1474     DisplayMessage(buf, "");
1475     free(p); appData.results = q;
1476     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1477        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1478       int round = appData.defaultMatchGames * appData.tourneyType;
1479       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1480          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1481         UnloadEngine(&first);  // next game belongs to other pairing;
1482         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1483     }
1484     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1485 }
1486
1487 void
1488 MatchEvent (int mode)
1489 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1490         int dummy;
1491         if(matchMode) { // already in match mode: switch it off
1492             abortMatch = TRUE;
1493             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1494             return;
1495         }
1496 //      if(gameMode != BeginningOfGame) {
1497 //          DisplayError(_("You can only start a match from the initial position."), 0);
1498 //          return;
1499 //      }
1500         abortMatch = FALSE;
1501         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1502         /* Set up machine vs. machine match */
1503         nextGame = 0;
1504         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1505         if(appData.tourneyFile[0]) {
1506             ReserveGame(-1, 0);
1507             if(nextGame > appData.matchGames) {
1508                 char buf[MSG_SIZ];
1509                 if(strchr(appData.results, '*') == NULL) {
1510                     FILE *f;
1511                     appData.tourneyCycles++;
1512                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1513                         fclose(f);
1514                         NextTourneyGame(-1, &dummy);
1515                         ReserveGame(-1, 0);
1516                         if(nextGame <= appData.matchGames) {
1517                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1518                             matchMode = mode;
1519                             ScheduleDelayedEvent(NextMatchGame, 10000);
1520                             return;
1521                         }
1522                     }
1523                 }
1524                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1525                 DisplayError(buf, 0);
1526                 appData.tourneyFile[0] = 0;
1527                 return;
1528             }
1529         } else
1530         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1531             DisplayFatalError(_("Can't have a match with no chess programs"),
1532                               0, 2);
1533             return;
1534         }
1535         matchMode = mode;
1536         matchGame = roundNr = 1;
1537         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1538         NextMatchGame();
1539 }
1540
1541 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1542
1543 void
1544 InitBackEnd3 P((void))
1545 {
1546     GameMode initialMode;
1547     char buf[MSG_SIZ];
1548     int err, len;
1549
1550     InitChessProgram(&first, startedFromSetupPosition);
1551
1552     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1553         free(programVersion);
1554         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1555         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1556         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1557     }
1558
1559     if (appData.icsActive) {
1560 #ifdef WIN32
1561         /* [DM] Make a console window if needed [HGM] merged ifs */
1562         ConsoleCreate();
1563 #endif
1564         err = establish();
1565         if (err != 0)
1566           {
1567             if (*appData.icsCommPort != NULLCHAR)
1568               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1569                              appData.icsCommPort);
1570             else
1571               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1572                         appData.icsHost, appData.icsPort);
1573
1574             if( (len >= MSG_SIZ) && appData.debugMode )
1575               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1576
1577             DisplayFatalError(buf, err, 1);
1578             return;
1579         }
1580         SetICSMode();
1581         telnetISR =
1582           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1583         fromUserISR =
1584           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1585         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1586             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1587     } else if (appData.noChessProgram) {
1588         SetNCPMode();
1589     } else {
1590         SetGNUMode();
1591     }
1592
1593     if (*appData.cmailGameName != NULLCHAR) {
1594         SetCmailMode();
1595         OpenLoopback(&cmailPR);
1596         cmailISR =
1597           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1598     }
1599
1600     ThawUI();
1601     DisplayMessage("", "");
1602     if (StrCaseCmp(appData.initialMode, "") == 0) {
1603       initialMode = BeginningOfGame;
1604       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1605         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1606         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1607         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1608         ModeHighlight();
1609       }
1610     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1611       initialMode = TwoMachinesPlay;
1612     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1613       initialMode = AnalyzeFile;
1614     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1615       initialMode = AnalyzeMode;
1616     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1617       initialMode = MachinePlaysWhite;
1618     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1619       initialMode = MachinePlaysBlack;
1620     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1621       initialMode = EditGame;
1622     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1623       initialMode = EditPosition;
1624     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1625       initialMode = Training;
1626     } else {
1627       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1628       if( (len >= MSG_SIZ) && appData.debugMode )
1629         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631       DisplayFatalError(buf, 0, 2);
1632       return;
1633     }
1634
1635     if (appData.matchMode) {
1636         if(appData.tourneyFile[0]) { // start tourney from command line
1637             FILE *f;
1638             if(f = fopen(appData.tourneyFile, "r")) {
1639                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1640                 fclose(f);
1641                 appData.clockMode = TRUE;
1642                 SetGNUMode();
1643             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1644         }
1645         MatchEvent(TRUE);
1646     } else if (*appData.cmailGameName != NULLCHAR) {
1647         /* Set up cmail mode */
1648         ReloadCmailMsgEvent(TRUE);
1649     } else {
1650         /* Set up other modes */
1651         if (initialMode == AnalyzeFile) {
1652           if (*appData.loadGameFile == NULLCHAR) {
1653             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1654             return;
1655           }
1656         }
1657         if (*appData.loadGameFile != NULLCHAR) {
1658             (void) LoadGameFromFile(appData.loadGameFile,
1659                                     appData.loadGameIndex,
1660                                     appData.loadGameFile, TRUE);
1661         } else if (*appData.loadPositionFile != NULLCHAR) {
1662             (void) LoadPositionFromFile(appData.loadPositionFile,
1663                                         appData.loadPositionIndex,
1664                                         appData.loadPositionFile);
1665             /* [HGM] try to make self-starting even after FEN load */
1666             /* to allow automatic setup of fairy variants with wtm */
1667             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1668                 gameMode = BeginningOfGame;
1669                 setboardSpoiledMachineBlack = 1;
1670             }
1671             /* [HGM] loadPos: make that every new game uses the setup */
1672             /* from file as long as we do not switch variant          */
1673             if(!blackPlaysFirst) {
1674                 startedFromPositionFile = TRUE;
1675                 CopyBoard(filePosition, boards[0]);
1676             }
1677         }
1678         if (initialMode == AnalyzeMode) {
1679           if (appData.noChessProgram) {
1680             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1681             return;
1682           }
1683           if (appData.icsActive) {
1684             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1685             return;
1686           }
1687           AnalyzeModeEvent();
1688         } else if (initialMode == AnalyzeFile) {
1689           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1690           ShowThinkingEvent();
1691           AnalyzeFileEvent();
1692           AnalysisPeriodicEvent(1);
1693         } else if (initialMode == MachinePlaysWhite) {
1694           if (appData.noChessProgram) {
1695             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1696                               0, 2);
1697             return;
1698           }
1699           if (appData.icsActive) {
1700             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1701                               0, 2);
1702             return;
1703           }
1704           MachineWhiteEvent();
1705         } else if (initialMode == MachinePlaysBlack) {
1706           if (appData.noChessProgram) {
1707             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1708                               0, 2);
1709             return;
1710           }
1711           if (appData.icsActive) {
1712             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1713                               0, 2);
1714             return;
1715           }
1716           MachineBlackEvent();
1717         } else if (initialMode == TwoMachinesPlay) {
1718           if (appData.noChessProgram) {
1719             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1720                               0, 2);
1721             return;
1722           }
1723           if (appData.icsActive) {
1724             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1725                               0, 2);
1726             return;
1727           }
1728           TwoMachinesEvent();
1729         } else if (initialMode == EditGame) {
1730           EditGameEvent();
1731         } else if (initialMode == EditPosition) {
1732           EditPositionEvent();
1733         } else if (initialMode == Training) {
1734           if (*appData.loadGameFile == NULLCHAR) {
1735             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1736             return;
1737           }
1738           TrainingEvent();
1739         }
1740     }
1741 }
1742
1743 void
1744 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1745 {
1746     DisplayBook(current+1);
1747
1748     MoveHistorySet( movelist, first, last, current, pvInfoList );
1749
1750     EvalGraphSet( first, last, current, pvInfoList );
1751
1752     MakeEngineOutputTitle();
1753 }
1754
1755 /*
1756  * Establish will establish a contact to a remote host.port.
1757  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1758  *  used to talk to the host.
1759  * Returns 0 if okay, error code if not.
1760  */
1761 int
1762 establish ()
1763 {
1764     char buf[MSG_SIZ];
1765
1766     if (*appData.icsCommPort != NULLCHAR) {
1767         /* Talk to the host through a serial comm port */
1768         return OpenCommPort(appData.icsCommPort, &icsPR);
1769
1770     } else if (*appData.gateway != NULLCHAR) {
1771         if (*appData.remoteShell == NULLCHAR) {
1772             /* Use the rcmd protocol to run telnet program on a gateway host */
1773             snprintf(buf, sizeof(buf), "%s %s %s",
1774                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1775             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1776
1777         } else {
1778             /* Use the rsh program to run telnet program on a gateway host */
1779             if (*appData.remoteUser == NULLCHAR) {
1780                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1781                         appData.gateway, appData.telnetProgram,
1782                         appData.icsHost, appData.icsPort);
1783             } else {
1784                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1785                         appData.remoteShell, appData.gateway,
1786                         appData.remoteUser, appData.telnetProgram,
1787                         appData.icsHost, appData.icsPort);
1788             }
1789             return StartChildProcess(buf, "", &icsPR);
1790
1791         }
1792     } else if (appData.useTelnet) {
1793         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1794
1795     } else {
1796         /* TCP socket interface differs somewhat between
1797            Unix and NT; handle details in the front end.
1798            */
1799         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1800     }
1801 }
1802
1803 void
1804 EscapeExpand (char *p, char *q)
1805 {       // [HGM] initstring: routine to shape up string arguments
1806         while(*p++ = *q++) if(p[-1] == '\\')
1807             switch(*q++) {
1808                 case 'n': p[-1] = '\n'; break;
1809                 case 'r': p[-1] = '\r'; break;
1810                 case 't': p[-1] = '\t'; break;
1811                 case '\\': p[-1] = '\\'; break;
1812                 case 0: *p = 0; return;
1813                 default: p[-1] = q[-1]; break;
1814             }
1815 }
1816
1817 void
1818 show_bytes (FILE *fp, char *buf, int count)
1819 {
1820     while (count--) {
1821         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1822             fprintf(fp, "\\%03o", *buf & 0xff);
1823         } else {
1824             putc(*buf, fp);
1825         }
1826         buf++;
1827     }
1828     fflush(fp);
1829 }
1830
1831 /* Returns an errno value */
1832 int
1833 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1834 {
1835     char buf[8192], *p, *q, *buflim;
1836     int left, newcount, outcount;
1837
1838     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1839         *appData.gateway != NULLCHAR) {
1840         if (appData.debugMode) {
1841             fprintf(debugFP, ">ICS: ");
1842             show_bytes(debugFP, message, count);
1843             fprintf(debugFP, "\n");
1844         }
1845         return OutputToProcess(pr, message, count, outError);
1846     }
1847
1848     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1849     p = message;
1850     q = buf;
1851     left = count;
1852     newcount = 0;
1853     while (left) {
1854         if (q >= buflim) {
1855             if (appData.debugMode) {
1856                 fprintf(debugFP, ">ICS: ");
1857                 show_bytes(debugFP, buf, newcount);
1858                 fprintf(debugFP, "\n");
1859             }
1860             outcount = OutputToProcess(pr, buf, newcount, outError);
1861             if (outcount < newcount) return -1; /* to be sure */
1862             q = buf;
1863             newcount = 0;
1864         }
1865         if (*p == '\n') {
1866             *q++ = '\r';
1867             newcount++;
1868         } else if (((unsigned char) *p) == TN_IAC) {
1869             *q++ = (char) TN_IAC;
1870             newcount ++;
1871         }
1872         *q++ = *p++;
1873         newcount++;
1874         left--;
1875     }
1876     if (appData.debugMode) {
1877         fprintf(debugFP, ">ICS: ");
1878         show_bytes(debugFP, buf, newcount);
1879         fprintf(debugFP, "\n");
1880     }
1881     outcount = OutputToProcess(pr, buf, newcount, outError);
1882     if (outcount < newcount) return -1; /* to be sure */
1883     return count;
1884 }
1885
1886 void
1887 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1888 {
1889     int outError, outCount;
1890     static int gotEof = 0;
1891     static FILE *ini;
1892
1893     /* Pass data read from player on to ICS */
1894     if (count > 0) {
1895         gotEof = 0;
1896         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1897         if (outCount < count) {
1898             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1899         }
1900         if(have_sent_ICS_logon == 2) {
1901           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1902             fprintf(ini, "%s", message);
1903             have_sent_ICS_logon = 3;
1904           } else
1905             have_sent_ICS_logon = 1;
1906         } else if(have_sent_ICS_logon == 3) {
1907             fprintf(ini, "%s", message);
1908             fclose(ini);
1909           have_sent_ICS_logon = 1;
1910         }
1911     } else if (count < 0) {
1912         RemoveInputSource(isr);
1913         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1914     } else if (gotEof++ > 0) {
1915         RemoveInputSource(isr);
1916         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1917     }
1918 }
1919
1920 void
1921 KeepAlive ()
1922 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1923     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1924     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1925     SendToICS("date\n");
1926     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1927 }
1928
1929 /* added routine for printf style output to ics */
1930 void
1931 ics_printf (char *format, ...)
1932 {
1933     char buffer[MSG_SIZ];
1934     va_list args;
1935
1936     va_start(args, format);
1937     vsnprintf(buffer, sizeof(buffer), format, args);
1938     buffer[sizeof(buffer)-1] = '\0';
1939     SendToICS(buffer);
1940     va_end(args);
1941 }
1942
1943 void
1944 SendToICS (char *s)
1945 {
1946     int count, outCount, outError;
1947
1948     if (icsPR == NoProc) return;
1949
1950     count = strlen(s);
1951     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1952     if (outCount < count) {
1953         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1954     }
1955 }
1956
1957 /* This is used for sending logon scripts to the ICS. Sending
1958    without a delay causes problems when using timestamp on ICC
1959    (at least on my machine). */
1960 void
1961 SendToICSDelayed (char *s, long msdelay)
1962 {
1963     int count, outCount, outError;
1964
1965     if (icsPR == NoProc) return;
1966
1967     count = strlen(s);
1968     if (appData.debugMode) {
1969         fprintf(debugFP, ">ICS: ");
1970         show_bytes(debugFP, s, count);
1971         fprintf(debugFP, "\n");
1972     }
1973     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1974                                       msdelay);
1975     if (outCount < count) {
1976         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1977     }
1978 }
1979
1980
1981 /* Remove all highlighting escape sequences in s
1982    Also deletes any suffix starting with '('
1983    */
1984 char *
1985 StripHighlightAndTitle (char *s)
1986 {
1987     static char retbuf[MSG_SIZ];
1988     char *p = retbuf;
1989
1990     while (*s != NULLCHAR) {
1991         while (*s == '\033') {
1992             while (*s != NULLCHAR && !isalpha(*s)) s++;
1993             if (*s != NULLCHAR) s++;
1994         }
1995         while (*s != NULLCHAR && *s != '\033') {
1996             if (*s == '(' || *s == '[') {
1997                 *p = NULLCHAR;
1998                 return retbuf;
1999             }
2000             *p++ = *s++;
2001         }
2002     }
2003     *p = NULLCHAR;
2004     return retbuf;
2005 }
2006
2007 /* Remove all highlighting escape sequences in s */
2008 char *
2009 StripHighlight (char *s)
2010 {
2011     static char retbuf[MSG_SIZ];
2012     char *p = retbuf;
2013
2014     while (*s != NULLCHAR) {
2015         while (*s == '\033') {
2016             while (*s != NULLCHAR && !isalpha(*s)) s++;
2017             if (*s != NULLCHAR) s++;
2018         }
2019         while (*s != NULLCHAR && *s != '\033') {
2020             *p++ = *s++;
2021         }
2022     }
2023     *p = NULLCHAR;
2024     return retbuf;
2025 }
2026
2027 char engineVariant[MSG_SIZ];
2028 char *variantNames[] = VARIANT_NAMES;
2029 char *
2030 VariantName (VariantClass v)
2031 {
2032     if(v == VariantUnknown || *engineVariant) return engineVariant;
2033     return variantNames[v];
2034 }
2035
2036
2037 /* Identify a variant from the strings the chess servers use or the
2038    PGN Variant tag names we use. */
2039 VariantClass
2040 StringToVariant (char *e)
2041 {
2042     char *p;
2043     int wnum = -1;
2044     VariantClass v = VariantNormal;
2045     int i, found = FALSE;
2046     char buf[MSG_SIZ];
2047     int len;
2048
2049     if (!e) return v;
2050
2051     /* [HGM] skip over optional board-size prefixes */
2052     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2053         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2054         while( *e++ != '_');
2055     }
2056
2057     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2058         v = VariantNormal;
2059         found = TRUE;
2060     } else
2061     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2062       if (StrCaseStr(e, variantNames[i])) {
2063         v = (VariantClass) i;
2064         found = TRUE;
2065         break;
2066       }
2067     }
2068
2069     if (!found) {
2070       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2071           || StrCaseStr(e, "wild/fr")
2072           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2073         v = VariantFischeRandom;
2074       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2075                  (i = 1, p = StrCaseStr(e, "w"))) {
2076         p += i;
2077         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2078         if (isdigit(*p)) {
2079           wnum = atoi(p);
2080         } else {
2081           wnum = -1;
2082         }
2083         switch (wnum) {
2084         case 0: /* FICS only, actually */
2085         case 1:
2086           /* Castling legal even if K starts on d-file */
2087           v = VariantWildCastle;
2088           break;
2089         case 2:
2090         case 3:
2091         case 4:
2092           /* Castling illegal even if K & R happen to start in
2093              normal positions. */
2094           v = VariantNoCastle;
2095           break;
2096         case 5:
2097         case 7:
2098         case 8:
2099         case 10:
2100         case 11:
2101         case 12:
2102         case 13:
2103         case 14:
2104         case 15:
2105         case 18:
2106         case 19:
2107           /* Castling legal iff K & R start in normal positions */
2108           v = VariantNormal;
2109           break;
2110         case 6:
2111         case 20:
2112         case 21:
2113           /* Special wilds for position setup; unclear what to do here */
2114           v = VariantLoadable;
2115           break;
2116         case 9:
2117           /* Bizarre ICC game */
2118           v = VariantTwoKings;
2119           break;
2120         case 16:
2121           v = VariantKriegspiel;
2122           break;
2123         case 17:
2124           v = VariantLosers;
2125           break;
2126         case 22:
2127           v = VariantFischeRandom;
2128           break;
2129         case 23:
2130           v = VariantCrazyhouse;
2131           break;
2132         case 24:
2133           v = VariantBughouse;
2134           break;
2135         case 25:
2136           v = Variant3Check;
2137           break;
2138         case 26:
2139           /* Not quite the same as FICS suicide! */
2140           v = VariantGiveaway;
2141           break;
2142         case 27:
2143           v = VariantAtomic;
2144           break;
2145         case 28:
2146           v = VariantShatranj;
2147           break;
2148
2149         /* Temporary names for future ICC types.  The name *will* change in
2150            the next xboard/WinBoard release after ICC defines it. */
2151         case 29:
2152           v = Variant29;
2153           break;
2154         case 30:
2155           v = Variant30;
2156           break;
2157         case 31:
2158           v = Variant31;
2159           break;
2160         case 32:
2161           v = Variant32;
2162           break;
2163         case 33:
2164           v = Variant33;
2165           break;
2166         case 34:
2167           v = Variant34;
2168           break;
2169         case 35:
2170           v = Variant35;
2171           break;
2172         case 36:
2173           v = Variant36;
2174           break;
2175         case 37:
2176           v = VariantShogi;
2177           break;
2178         case 38:
2179           v = VariantXiangqi;
2180           break;
2181         case 39:
2182           v = VariantCourier;
2183           break;
2184         case 40:
2185           v = VariantGothic;
2186           break;
2187         case 41:
2188           v = VariantCapablanca;
2189           break;
2190         case 42:
2191           v = VariantKnightmate;
2192           break;
2193         case 43:
2194           v = VariantFairy;
2195           break;
2196         case 44:
2197           v = VariantCylinder;
2198           break;
2199         case 45:
2200           v = VariantFalcon;
2201           break;
2202         case 46:
2203           v = VariantCapaRandom;
2204           break;
2205         case 47:
2206           v = VariantBerolina;
2207           break;
2208         case 48:
2209           v = VariantJanus;
2210           break;
2211         case 49:
2212           v = VariantSuper;
2213           break;
2214         case 50:
2215           v = VariantGreat;
2216           break;
2217         case -1:
2218           /* Found "wild" or "w" in the string but no number;
2219              must assume it's normal chess. */
2220           v = VariantNormal;
2221           break;
2222         default:
2223           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2224           if( (len >= MSG_SIZ) && appData.debugMode )
2225             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2226
2227           DisplayError(buf, 0);
2228           v = VariantUnknown;
2229           break;
2230         }
2231       }
2232     }
2233     if (appData.debugMode) {
2234       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2235               e, wnum, VariantName(v));
2236     }
2237     return v;
2238 }
2239
2240 static int leftover_start = 0, leftover_len = 0;
2241 char star_match[STAR_MATCH_N][MSG_SIZ];
2242
2243 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2244    advance *index beyond it, and set leftover_start to the new value of
2245    *index; else return FALSE.  If pattern contains the character '*', it
2246    matches any sequence of characters not containing '\r', '\n', or the
2247    character following the '*' (if any), and the matched sequence(s) are
2248    copied into star_match.
2249    */
2250 int
2251 looking_at ( char *buf, int *index, char *pattern)
2252 {
2253     char *bufp = &buf[*index], *patternp = pattern;
2254     int star_count = 0;
2255     char *matchp = star_match[0];
2256
2257     for (;;) {
2258         if (*patternp == NULLCHAR) {
2259             *index = leftover_start = bufp - buf;
2260             *matchp = NULLCHAR;
2261             return TRUE;
2262         }
2263         if (*bufp == NULLCHAR) return FALSE;
2264         if (*patternp == '*') {
2265             if (*bufp == *(patternp + 1)) {
2266                 *matchp = NULLCHAR;
2267                 matchp = star_match[++star_count];
2268                 patternp += 2;
2269                 bufp++;
2270                 continue;
2271             } else if (*bufp == '\n' || *bufp == '\r') {
2272                 patternp++;
2273                 if (*patternp == NULLCHAR)
2274                   continue;
2275                 else
2276                   return FALSE;
2277             } else {
2278                 *matchp++ = *bufp++;
2279                 continue;
2280             }
2281         }
2282         if (*patternp != *bufp) return FALSE;
2283         patternp++;
2284         bufp++;
2285     }
2286 }
2287
2288 void
2289 SendToPlayer (char *data, int length)
2290 {
2291     int error, outCount;
2292     outCount = OutputToProcess(NoProc, data, length, &error);
2293     if (outCount < length) {
2294         DisplayFatalError(_("Error writing to display"), error, 1);
2295     }
2296 }
2297
2298 void
2299 PackHolding (char packed[], char *holding)
2300 {
2301     char *p = holding;
2302     char *q = packed;
2303     int runlength = 0;
2304     int curr = 9999;
2305     do {
2306         if (*p == curr) {
2307             runlength++;
2308         } else {
2309             switch (runlength) {
2310               case 0:
2311                 break;
2312               case 1:
2313                 *q++ = curr;
2314                 break;
2315               case 2:
2316                 *q++ = curr;
2317                 *q++ = curr;
2318                 break;
2319               default:
2320                 sprintf(q, "%d", runlength);
2321                 while (*q) q++;
2322                 *q++ = curr;
2323                 break;
2324             }
2325             runlength = 1;
2326             curr = *p;
2327         }
2328     } while (*p++);
2329     *q = NULLCHAR;
2330 }
2331
2332 /* Telnet protocol requests from the front end */
2333 void
2334 TelnetRequest (unsigned char ddww, unsigned char option)
2335 {
2336     unsigned char msg[3];
2337     int outCount, outError;
2338
2339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2340
2341     if (appData.debugMode) {
2342         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2343         switch (ddww) {
2344           case TN_DO:
2345             ddwwStr = "DO";
2346             break;
2347           case TN_DONT:
2348             ddwwStr = "DONT";
2349             break;
2350           case TN_WILL:
2351             ddwwStr = "WILL";
2352             break;
2353           case TN_WONT:
2354             ddwwStr = "WONT";
2355             break;
2356           default:
2357             ddwwStr = buf1;
2358             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2359             break;
2360         }
2361         switch (option) {
2362           case TN_ECHO:
2363             optionStr = "ECHO";
2364             break;
2365           default:
2366             optionStr = buf2;
2367             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2368             break;
2369         }
2370         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2371     }
2372     msg[0] = TN_IAC;
2373     msg[1] = ddww;
2374     msg[2] = option;
2375     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2376     if (outCount < 3) {
2377         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2378     }
2379 }
2380
2381 void
2382 DoEcho ()
2383 {
2384     if (!appData.icsActive) return;
2385     TelnetRequest(TN_DO, TN_ECHO);
2386 }
2387
2388 void
2389 DontEcho ()
2390 {
2391     if (!appData.icsActive) return;
2392     TelnetRequest(TN_DONT, TN_ECHO);
2393 }
2394
2395 void
2396 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2397 {
2398     /* put the holdings sent to us by the server on the board holdings area */
2399     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2400     char p;
2401     ChessSquare piece;
2402
2403     if(gameInfo.holdingsWidth < 2)  return;
2404     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2405         return; // prevent overwriting by pre-board holdings
2406
2407     if( (int)lowestPiece >= BlackPawn ) {
2408         holdingsColumn = 0;
2409         countsColumn = 1;
2410         holdingsStartRow = BOARD_HEIGHT-1;
2411         direction = -1;
2412     } else {
2413         holdingsColumn = BOARD_WIDTH-1;
2414         countsColumn = BOARD_WIDTH-2;
2415         holdingsStartRow = 0;
2416         direction = 1;
2417     }
2418
2419     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2420         board[i][holdingsColumn] = EmptySquare;
2421         board[i][countsColumn]   = (ChessSquare) 0;
2422     }
2423     while( (p=*holdings++) != NULLCHAR ) {
2424         piece = CharToPiece( ToUpper(p) );
2425         if(piece == EmptySquare) continue;
2426         /*j = (int) piece - (int) WhitePawn;*/
2427         j = PieceToNumber(piece);
2428         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2429         if(j < 0) continue;               /* should not happen */
2430         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2431         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2432         board[holdingsStartRow+j*direction][countsColumn]++;
2433     }
2434 }
2435
2436
2437 void
2438 VariantSwitch (Board board, VariantClass newVariant)
2439 {
2440    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2441    static Board oldBoard;
2442
2443    startedFromPositionFile = FALSE;
2444    if(gameInfo.variant == newVariant) return;
2445
2446    /* [HGM] This routine is called each time an assignment is made to
2447     * gameInfo.variant during a game, to make sure the board sizes
2448     * are set to match the new variant. If that means adding or deleting
2449     * holdings, we shift the playing board accordingly
2450     * This kludge is needed because in ICS observe mode, we get boards
2451     * of an ongoing game without knowing the variant, and learn about the
2452     * latter only later. This can be because of the move list we requested,
2453     * in which case the game history is refilled from the beginning anyway,
2454     * but also when receiving holdings of a crazyhouse game. In the latter
2455     * case we want to add those holdings to the already received position.
2456     */
2457
2458
2459    if (appData.debugMode) {
2460      fprintf(debugFP, "Switch board from %s to %s\n",
2461              VariantName(gameInfo.variant), VariantName(newVariant));
2462      setbuf(debugFP, NULL);
2463    }
2464    shuffleOpenings = 0;       /* [HGM] shuffle */
2465    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2466    switch(newVariant)
2467      {
2468      case VariantShogi:
2469        newWidth = 9;  newHeight = 9;
2470        gameInfo.holdingsSize = 7;
2471      case VariantBughouse:
2472      case VariantCrazyhouse:
2473        newHoldingsWidth = 2; break;
2474      case VariantGreat:
2475        newWidth = 10;
2476      case VariantSuper:
2477        newHoldingsWidth = 2;
2478        gameInfo.holdingsSize = 8;
2479        break;
2480      case VariantGothic:
2481      case VariantCapablanca:
2482      case VariantCapaRandom:
2483        newWidth = 10;
2484      default:
2485        newHoldingsWidth = gameInfo.holdingsSize = 0;
2486      };
2487
2488    if(newWidth  != gameInfo.boardWidth  ||
2489       newHeight != gameInfo.boardHeight ||
2490       newHoldingsWidth != gameInfo.holdingsWidth ) {
2491
2492      /* shift position to new playing area, if needed */
2493      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2494        for(i=0; i<BOARD_HEIGHT; i++)
2495          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2496            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2497              board[i][j];
2498        for(i=0; i<newHeight; i++) {
2499          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2500          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2501        }
2502      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2503        for(i=0; i<BOARD_HEIGHT; i++)
2504          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2505            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2506              board[i][j];
2507      }
2508      board[HOLDINGS_SET] = 0;
2509      gameInfo.boardWidth  = newWidth;
2510      gameInfo.boardHeight = newHeight;
2511      gameInfo.holdingsWidth = newHoldingsWidth;
2512      gameInfo.variant = newVariant;
2513      InitDrawingSizes(-2, 0);
2514    } else gameInfo.variant = newVariant;
2515    CopyBoard(oldBoard, board);   // remember correctly formatted board
2516      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2517    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2518 }
2519
2520 static int loggedOn = FALSE;
2521
2522 /*-- Game start info cache: --*/
2523 int gs_gamenum;
2524 char gs_kind[MSG_SIZ];
2525 static char player1Name[128] = "";
2526 static char player2Name[128] = "";
2527 static char cont_seq[] = "\n\\   ";
2528 static int player1Rating = -1;
2529 static int player2Rating = -1;
2530 /*----------------------------*/
2531
2532 ColorClass curColor = ColorNormal;
2533 int suppressKibitz = 0;
2534
2535 // [HGM] seekgraph
2536 Boolean soughtPending = FALSE;
2537 Boolean seekGraphUp;
2538 #define MAX_SEEK_ADS 200
2539 #define SQUARE 0x80
2540 char *seekAdList[MAX_SEEK_ADS];
2541 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2542 float tcList[MAX_SEEK_ADS];
2543 char colorList[MAX_SEEK_ADS];
2544 int nrOfSeekAds = 0;
2545 int minRating = 1010, maxRating = 2800;
2546 int hMargin = 10, vMargin = 20, h, w;
2547 extern int squareSize, lineGap;
2548
2549 void
2550 PlotSeekAd (int i)
2551 {
2552         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2553         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2554         if(r < minRating+100 && r >=0 ) r = minRating+100;
2555         if(r > maxRating) r = maxRating;
2556         if(tc < 1.f) tc = 1.f;
2557         if(tc > 95.f) tc = 95.f;
2558         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2559         y = ((double)r - minRating)/(maxRating - minRating)
2560             * (h-vMargin-squareSize/8-1) + vMargin;
2561         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2562         if(strstr(seekAdList[i], " u ")) color = 1;
2563         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2564            !strstr(seekAdList[i], "bullet") &&
2565            !strstr(seekAdList[i], "blitz") &&
2566            !strstr(seekAdList[i], "standard") ) color = 2;
2567         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2568         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2569 }
2570
2571 void
2572 PlotSingleSeekAd (int i)
2573 {
2574         PlotSeekAd(i);
2575 }
2576
2577 void
2578 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2579 {
2580         char buf[MSG_SIZ], *ext = "";
2581         VariantClass v = StringToVariant(type);
2582         if(strstr(type, "wild")) {
2583             ext = type + 4; // append wild number
2584             if(v == VariantFischeRandom) type = "chess960"; else
2585             if(v == VariantLoadable) type = "setup"; else
2586             type = VariantName(v);
2587         }
2588         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2589         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2590             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2591             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2592             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2593             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2594             seekNrList[nrOfSeekAds] = nr;
2595             zList[nrOfSeekAds] = 0;
2596             seekAdList[nrOfSeekAds++] = StrSave(buf);
2597             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2598         }
2599 }
2600
2601 void
2602 EraseSeekDot (int i)
2603 {
2604     int x = xList[i], y = yList[i], d=squareSize/4, k;
2605     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2606     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2607     // now replot every dot that overlapped
2608     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2609         int xx = xList[k], yy = yList[k];
2610         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2611             DrawSeekDot(xx, yy, colorList[k]);
2612     }
2613 }
2614
2615 void
2616 RemoveSeekAd (int nr)
2617 {
2618         int i;
2619         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2620             EraseSeekDot(i);
2621             if(seekAdList[i]) free(seekAdList[i]);
2622             seekAdList[i] = seekAdList[--nrOfSeekAds];
2623             seekNrList[i] = seekNrList[nrOfSeekAds];
2624             ratingList[i] = ratingList[nrOfSeekAds];
2625             colorList[i]  = colorList[nrOfSeekAds];
2626             tcList[i] = tcList[nrOfSeekAds];
2627             xList[i]  = xList[nrOfSeekAds];
2628             yList[i]  = yList[nrOfSeekAds];
2629             zList[i]  = zList[nrOfSeekAds];
2630             seekAdList[nrOfSeekAds] = NULL;
2631             break;
2632         }
2633 }
2634
2635 Boolean
2636 MatchSoughtLine (char *line)
2637 {
2638     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2639     int nr, base, inc, u=0; char dummy;
2640
2641     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2642        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2643        (u=1) &&
2644        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2645         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2646         // match: compact and save the line
2647         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2648         return TRUE;
2649     }
2650     return FALSE;
2651 }
2652
2653 int
2654 DrawSeekGraph ()
2655 {
2656     int i;
2657     if(!seekGraphUp) return FALSE;
2658     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2659     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2660
2661     DrawSeekBackground(0, 0, w, h);
2662     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2663     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2664     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2665         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2666         yy = h-1-yy;
2667         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2668         if(i%500 == 0) {
2669             char buf[MSG_SIZ];
2670             snprintf(buf, MSG_SIZ, "%d", i);
2671             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2672         }
2673     }
2674     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2675     for(i=1; i<100; i+=(i<10?1:5)) {
2676         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2677         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2678         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2679             char buf[MSG_SIZ];
2680             snprintf(buf, MSG_SIZ, "%d", i);
2681             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2682         }
2683     }
2684     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2685     return TRUE;
2686 }
2687
2688 int
2689 SeekGraphClick (ClickType click, int x, int y, int moving)
2690 {
2691     static int lastDown = 0, displayed = 0, lastSecond;
2692     if(y < 0) return FALSE;
2693     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2694         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2695         if(!seekGraphUp) return FALSE;
2696         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2697         DrawPosition(TRUE, NULL);
2698         return TRUE;
2699     }
2700     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2701         if(click == Release || moving) return FALSE;
2702         nrOfSeekAds = 0;
2703         soughtPending = TRUE;
2704         SendToICS(ics_prefix);
2705         SendToICS("sought\n"); // should this be "sought all"?
2706     } else { // issue challenge based on clicked ad
2707         int dist = 10000; int i, closest = 0, second = 0;
2708         for(i=0; i<nrOfSeekAds; i++) {
2709             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2710             if(d < dist) { dist = d; closest = i; }
2711             second += (d - zList[i] < 120); // count in-range ads
2712             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2713         }
2714         if(dist < 120) {
2715             char buf[MSG_SIZ];
2716             second = (second > 1);
2717             if(displayed != closest || second != lastSecond) {
2718                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2719                 lastSecond = second; displayed = closest;
2720             }
2721             if(click == Press) {
2722                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2723                 lastDown = closest;
2724                 return TRUE;
2725             } // on press 'hit', only show info
2726             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2727             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2728             SendToICS(ics_prefix);
2729             SendToICS(buf);
2730             return TRUE; // let incoming board of started game pop down the graph
2731         } else if(click == Release) { // release 'miss' is ignored
2732             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2733             if(moving == 2) { // right up-click
2734                 nrOfSeekAds = 0; // refresh graph
2735                 soughtPending = TRUE;
2736                 SendToICS(ics_prefix);
2737                 SendToICS("sought\n"); // should this be "sought all"?
2738             }
2739             return TRUE;
2740         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2741         // press miss or release hit 'pop down' seek graph
2742         seekGraphUp = FALSE;
2743         DrawPosition(TRUE, NULL);
2744     }
2745     return TRUE;
2746 }
2747
2748 void
2749 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2750 {
2751 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2752 #define STARTED_NONE 0
2753 #define STARTED_MOVES 1
2754 #define STARTED_BOARD 2
2755 #define STARTED_OBSERVE 3
2756 #define STARTED_HOLDINGS 4
2757 #define STARTED_CHATTER 5
2758 #define STARTED_COMMENT 6
2759 #define STARTED_MOVES_NOHIDE 7
2760
2761     static int started = STARTED_NONE;
2762     static char parse[20000];
2763     static int parse_pos = 0;
2764     static char buf[BUF_SIZE + 1];
2765     static int firstTime = TRUE, intfSet = FALSE;
2766     static ColorClass prevColor = ColorNormal;
2767     static int savingComment = FALSE;
2768     static int cmatch = 0; // continuation sequence match
2769     char *bp;
2770     char str[MSG_SIZ];
2771     int i, oldi;
2772     int buf_len;
2773     int next_out;
2774     int tkind;
2775     int backup;    /* [DM] For zippy color lines */
2776     char *p;
2777     char talker[MSG_SIZ]; // [HGM] chat
2778     int channel;
2779
2780     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2781
2782     if (appData.debugMode) {
2783       if (!error) {
2784         fprintf(debugFP, "<ICS: ");
2785         show_bytes(debugFP, data, count);
2786         fprintf(debugFP, "\n");
2787       }
2788     }
2789
2790     if (appData.debugMode) { int f = forwardMostMove;
2791         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2792                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2793                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2794     }
2795     if (count > 0) {
2796         /* If last read ended with a partial line that we couldn't parse,
2797            prepend it to the new read and try again. */
2798         if (leftover_len > 0) {
2799             for (i=0; i<leftover_len; i++)
2800               buf[i] = buf[leftover_start + i];
2801         }
2802
2803     /* copy new characters into the buffer */
2804     bp = buf + leftover_len;
2805     buf_len=leftover_len;
2806     for (i=0; i<count; i++)
2807     {
2808         // ignore these
2809         if (data[i] == '\r')
2810             continue;
2811
2812         // join lines split by ICS?
2813         if (!appData.noJoin)
2814         {
2815             /*
2816                 Joining just consists of finding matches against the
2817                 continuation sequence, and discarding that sequence
2818                 if found instead of copying it.  So, until a match
2819                 fails, there's nothing to do since it might be the
2820                 complete sequence, and thus, something we don't want
2821                 copied.
2822             */
2823             if (data[i] == cont_seq[cmatch])
2824             {
2825                 cmatch++;
2826                 if (cmatch == strlen(cont_seq))
2827                 {
2828                     cmatch = 0; // complete match.  just reset the counter
2829
2830                     /*
2831                         it's possible for the ICS to not include the space
2832                         at the end of the last word, making our [correct]
2833                         join operation fuse two separate words.  the server
2834                         does this when the space occurs at the width setting.
2835                     */
2836                     if (!buf_len || buf[buf_len-1] != ' ')
2837                     {
2838                         *bp++ = ' ';
2839                         buf_len++;
2840                     }
2841                 }
2842                 continue;
2843             }
2844             else if (cmatch)
2845             {
2846                 /*
2847                     match failed, so we have to copy what matched before
2848                     falling through and copying this character.  In reality,
2849                     this will only ever be just the newline character, but
2850                     it doesn't hurt to be precise.
2851                 */
2852                 strncpy(bp, cont_seq, cmatch);
2853                 bp += cmatch;
2854                 buf_len += cmatch;
2855                 cmatch = 0;
2856             }
2857         }
2858
2859         // copy this char
2860         *bp++ = data[i];
2861         buf_len++;
2862     }
2863
2864         buf[buf_len] = NULLCHAR;
2865 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2866         next_out = 0;
2867         leftover_start = 0;
2868
2869         i = 0;
2870         while (i < buf_len) {
2871             /* Deal with part of the TELNET option negotiation
2872                protocol.  We refuse to do anything beyond the
2873                defaults, except that we allow the WILL ECHO option,
2874                which ICS uses to turn off password echoing when we are
2875                directly connected to it.  We reject this option
2876                if localLineEditing mode is on (always on in xboard)
2877                and we are talking to port 23, which might be a real
2878                telnet server that will try to keep WILL ECHO on permanently.
2879              */
2880             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2881                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2882                 unsigned char option;
2883                 oldi = i;
2884                 switch ((unsigned char) buf[++i]) {
2885                   case TN_WILL:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<WILL ");
2888                     switch (option = (unsigned char) buf[++i]) {
2889                       case TN_ECHO:
2890                         if (appData.debugMode)
2891                           fprintf(debugFP, "ECHO ");
2892                         /* Reply only if this is a change, according
2893                            to the protocol rules. */
2894                         if (remoteEchoOption) break;
2895                         if (appData.localLineEditing &&
2896                             atoi(appData.icsPort) == TN_PORT) {
2897                             TelnetRequest(TN_DONT, TN_ECHO);
2898                         } else {
2899                             EchoOff();
2900                             TelnetRequest(TN_DO, TN_ECHO);
2901                             remoteEchoOption = TRUE;
2902                         }
2903                         break;
2904                       default:
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", option);
2907                         /* Whatever this is, we don't want it. */
2908                         TelnetRequest(TN_DONT, option);
2909                         break;
2910                     }
2911                     break;
2912                   case TN_WONT:
2913                     if (appData.debugMode)
2914                       fprintf(debugFP, "\n<WONT ");
2915                     switch (option = (unsigned char) buf[++i]) {
2916                       case TN_ECHO:
2917                         if (appData.debugMode)
2918                           fprintf(debugFP, "ECHO ");
2919                         /* Reply only if this is a change, according
2920                            to the protocol rules. */
2921                         if (!remoteEchoOption) break;
2922                         EchoOn();
2923                         TelnetRequest(TN_DONT, TN_ECHO);
2924                         remoteEchoOption = FALSE;
2925                         break;
2926                       default:
2927                         if (appData.debugMode)
2928                           fprintf(debugFP, "%d ", (unsigned char) option);
2929                         /* Whatever this is, it must already be turned
2930                            off, because we never agree to turn on
2931                            anything non-default, so according to the
2932                            protocol rules, we don't reply. */
2933                         break;
2934                     }
2935                     break;
2936                   case TN_DO:
2937                     if (appData.debugMode)
2938                       fprintf(debugFP, "\n<DO ");
2939                     switch (option = (unsigned char) buf[++i]) {
2940                       default:
2941                         /* Whatever this is, we refuse to do it. */
2942                         if (appData.debugMode)
2943                           fprintf(debugFP, "%d ", option);
2944                         TelnetRequest(TN_WONT, option);
2945                         break;
2946                     }
2947                     break;
2948                   case TN_DONT:
2949                     if (appData.debugMode)
2950                       fprintf(debugFP, "\n<DONT ");
2951                     switch (option = (unsigned char) buf[++i]) {
2952                       default:
2953                         if (appData.debugMode)
2954                           fprintf(debugFP, "%d ", option);
2955                         /* Whatever this is, we are already not doing
2956                            it, because we never agree to do anything
2957                            non-default, so according to the protocol
2958                            rules, we don't reply. */
2959                         break;
2960                     }
2961                     break;
2962                   case TN_IAC:
2963                     if (appData.debugMode)
2964                       fprintf(debugFP, "\n<IAC ");
2965                     /* Doubled IAC; pass it through */
2966                     i--;
2967                     break;
2968                   default:
2969                     if (appData.debugMode)
2970                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2971                     /* Drop all other telnet commands on the floor */
2972                     break;
2973                 }
2974                 if (oldi > next_out)
2975                   SendToPlayer(&buf[next_out], oldi - next_out);
2976                 if (++i > next_out)
2977                   next_out = i;
2978                 continue;
2979             }
2980
2981             /* OK, this at least will *usually* work */
2982             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2983                 loggedOn = TRUE;
2984             }
2985
2986             if (loggedOn && !intfSet) {
2987                 if (ics_type == ICS_ICC) {
2988                   snprintf(str, MSG_SIZ,
2989                           "/set-quietly interface %s\n/set-quietly style 12\n",
2990                           programVersion);
2991                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2992                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2993                 } else if (ics_type == ICS_CHESSNET) {
2994                   snprintf(str, MSG_SIZ, "/style 12\n");
2995                 } else {
2996                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2997                   strcat(str, programVersion);
2998                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3001 #ifdef WIN32
3002                   strcat(str, "$iset nohighlight 1\n");
3003 #endif
3004                   strcat(str, "$iset lock 1\n$style 12\n");
3005                 }
3006                 SendToICS(str);
3007                 NotifyFrontendLogin();
3008                 intfSet = TRUE;
3009             }
3010
3011             if (started == STARTED_COMMENT) {
3012                 /* Accumulate characters in comment */
3013                 parse[parse_pos++] = buf[i];
3014                 if (buf[i] == '\n') {
3015                     parse[parse_pos] = NULLCHAR;
3016                     if(chattingPartner>=0) {
3017                         char mess[MSG_SIZ];
3018                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3019                         OutputChatMessage(chattingPartner, mess);
3020                         chattingPartner = -1;
3021                         next_out = i+1; // [HGM] suppress printing in ICS window
3022                     } else
3023                     if(!suppressKibitz) // [HGM] kibitz
3024                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3025                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3026                         int nrDigit = 0, nrAlph = 0, j;
3027                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3028                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3029                         parse[parse_pos] = NULLCHAR;
3030                         // try to be smart: if it does not look like search info, it should go to
3031                         // ICS interaction window after all, not to engine-output window.
3032                         for(j=0; j<parse_pos; j++) { // count letters and digits
3033                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3034                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3035                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3036                         }
3037                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3038                             int depth=0; float score;
3039                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3040                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3041                                 pvInfoList[forwardMostMove-1].depth = depth;
3042                                 pvInfoList[forwardMostMove-1].score = 100*score;
3043                             }
3044                             OutputKibitz(suppressKibitz, parse);
3045                         } else {
3046                             char tmp[MSG_SIZ];
3047                             if(gameMode == IcsObserving) // restore original ICS messages
3048                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3049                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3050                             else
3051                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3052                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3053                             SendToPlayer(tmp, strlen(tmp));
3054                         }
3055                         next_out = i+1; // [HGM] suppress printing in ICS window
3056                     }
3057                     started = STARTED_NONE;
3058                 } else {
3059                     /* Don't match patterns against characters in comment */
3060                     i++;
3061                     continue;
3062                 }
3063             }
3064             if (started == STARTED_CHATTER) {
3065                 if (buf[i] != '\n') {
3066                     /* Don't match patterns against characters in chatter */
3067                     i++;
3068                     continue;
3069                 }
3070                 started = STARTED_NONE;
3071                 if(suppressKibitz) next_out = i+1;
3072             }
3073
3074             /* Kludge to deal with rcmd protocol */
3075             if (firstTime && looking_at(buf, &i, "\001*")) {
3076                 DisplayFatalError(&buf[1], 0, 1);
3077                 continue;
3078             } else {
3079                 firstTime = FALSE;
3080             }
3081
3082             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3083                 ics_type = ICS_ICC;
3084                 ics_prefix = "/";
3085                 if (appData.debugMode)
3086                   fprintf(debugFP, "ics_type %d\n", ics_type);
3087                 continue;
3088             }
3089             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3090                 ics_type = ICS_FICS;
3091                 ics_prefix = "$";
3092                 if (appData.debugMode)
3093                   fprintf(debugFP, "ics_type %d\n", ics_type);
3094                 continue;
3095             }
3096             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3097                 ics_type = ICS_CHESSNET;
3098                 ics_prefix = "/";
3099                 if (appData.debugMode)
3100                   fprintf(debugFP, "ics_type %d\n", ics_type);
3101                 continue;
3102             }
3103
3104             if (!loggedOn &&
3105                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3106                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3107                  looking_at(buf, &i, "will be \"*\""))) {
3108               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3109               continue;
3110             }
3111
3112             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3113               char buf[MSG_SIZ];
3114               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3115               DisplayIcsInteractionTitle(buf);
3116               have_set_title = TRUE;
3117             }
3118
3119             /* skip finger notes */
3120             if (started == STARTED_NONE &&
3121                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3122                  (buf[i] == '1' && buf[i+1] == '0')) &&
3123                 buf[i+2] == ':' && buf[i+3] == ' ') {
3124               started = STARTED_CHATTER;
3125               i += 3;
3126               continue;
3127             }
3128
3129             oldi = i;
3130             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3131             if(appData.seekGraph) {
3132                 if(soughtPending && MatchSoughtLine(buf+i)) {
3133                     i = strstr(buf+i, "rated") - buf;
3134                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3135                     next_out = leftover_start = i;
3136                     started = STARTED_CHATTER;
3137                     suppressKibitz = TRUE;
3138                     continue;
3139                 }
3140                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3141                         && looking_at(buf, &i, "* ads displayed")) {
3142                     soughtPending = FALSE;
3143                     seekGraphUp = TRUE;
3144                     DrawSeekGraph();
3145                     continue;
3146                 }
3147                 if(appData.autoRefresh) {
3148                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3149                         int s = (ics_type == ICS_ICC); // ICC format differs
3150                         if(seekGraphUp)
3151                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3152                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3153                         looking_at(buf, &i, "*% "); // eat prompt
3154                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3155                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156                         next_out = i; // suppress
3157                         continue;
3158                     }
3159                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3160                         char *p = star_match[0];
3161                         while(*p) {
3162                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3163                             while(*p && *p++ != ' '); // next
3164                         }
3165                         looking_at(buf, &i, "*% "); // eat prompt
3166                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                         next_out = i;
3168                         continue;
3169                     }
3170                 }
3171             }
3172
3173             /* skip formula vars */
3174             if (started == STARTED_NONE &&
3175                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3176               started = STARTED_CHATTER;
3177               i += 3;
3178               continue;
3179             }
3180
3181             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3182             if (appData.autoKibitz && started == STARTED_NONE &&
3183                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3184                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3185                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3186                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3187                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3188                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3189                         suppressKibitz = TRUE;
3190                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                         next_out = i;
3192                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3193                                 && (gameMode == IcsPlayingWhite)) ||
3194                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3195                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3196                             started = STARTED_CHATTER; // own kibitz we simply discard
3197                         else {
3198                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3199                             parse_pos = 0; parse[0] = NULLCHAR;
3200                             savingComment = TRUE;
3201                             suppressKibitz = gameMode != IcsObserving ? 2 :
3202                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3203                         }
3204                         continue;
3205                 } else
3206                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3207                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3208                          && atoi(star_match[0])) {
3209                     // suppress the acknowledgements of our own autoKibitz
3210                     char *p;
3211                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3213                     SendToPlayer(star_match[0], strlen(star_match[0]));
3214                     if(looking_at(buf, &i, "*% ")) // eat prompt
3215                         suppressKibitz = FALSE;
3216                     next_out = i;
3217                     continue;
3218                 }
3219             } // [HGM] kibitz: end of patch
3220
3221             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3222
3223             // [HGM] chat: intercept tells by users for which we have an open chat window
3224             channel = -1;
3225             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3226                                            looking_at(buf, &i, "* whispers:") ||
3227                                            looking_at(buf, &i, "* kibitzes:") ||
3228                                            looking_at(buf, &i, "* shouts:") ||
3229                                            looking_at(buf, &i, "* c-shouts:") ||
3230                                            looking_at(buf, &i, "--> * ") ||
3231                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3232                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3233                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3234                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3235                 int p;
3236                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3237                 chattingPartner = -1;
3238
3239                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3240                 for(p=0; p<MAX_CHAT; p++) {
3241                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3242                     talker[0] = '['; strcat(talker, "] ");
3243                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3244                     chattingPartner = p; break;
3245                     }
3246                 } else
3247                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(!strcmp("kibitzes", chatPartner[p])) {
3250                         talker[0] = '['; strcat(talker, "] ");
3251                         chattingPartner = p; break;
3252                     }
3253                 } else
3254                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3255                 for(p=0; p<MAX_CHAT; p++) {
3256                     if(!strcmp("whispers", chatPartner[p])) {
3257                         talker[0] = '['; strcat(talker, "] ");
3258                         chattingPartner = p; break;
3259                     }
3260                 } else
3261                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3262                   if(buf[i-8] == '-' && buf[i-3] == 't')
3263                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3264                     if(!strcmp("c-shouts", chatPartner[p])) {
3265                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3266                         chattingPartner = p; break;
3267                     }
3268                   }
3269                   if(chattingPartner < 0)
3270                   for(p=0; p<MAX_CHAT; p++) {
3271                     if(!strcmp("shouts", chatPartner[p])) {
3272                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3273                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3274                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3275                         chattingPartner = p; break;
3276                     }
3277                   }
3278                 }
3279                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3280                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3281                     talker[0] = 0; Colorize(ColorTell, FALSE);
3282                     chattingPartner = p; break;
3283                 }
3284                 if(chattingPartner<0) i = oldi; else {
3285                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3286                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     started = STARTED_COMMENT;
3289                     parse_pos = 0; parse[0] = NULLCHAR;
3290                     savingComment = 3 + chattingPartner; // counts as TRUE
3291                     suppressKibitz = TRUE;
3292                     continue;
3293                 }
3294             } // [HGM] chat: end of patch
3295
3296           backup = i;
3297             if (appData.zippyTalk || appData.zippyPlay) {
3298                 /* [DM] Backup address for color zippy lines */
3299 #if ZIPPY
3300                if (loggedOn == TRUE)
3301                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3302                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3303 #endif
3304             } // [DM] 'else { ' deleted
3305                 if (
3306                     /* Regular tells and says */
3307                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3308                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3309                     looking_at(buf, &i, "* says: ") ||
3310                     /* Don't color "message" or "messages" output */
3311                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3312                     looking_at(buf, &i, "*. * at *:*: ") ||
3313                     looking_at(buf, &i, "--* (*:*): ") ||
3314                     /* Message notifications (same color as tells) */
3315                     looking_at(buf, &i, "* has left a message ") ||
3316                     looking_at(buf, &i, "* just sent you a message:\n") ||
3317                     /* Whispers and kibitzes */
3318                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3319                     looking_at(buf, &i, "* kibitzes: ") ||
3320                     /* Channel tells */
3321                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3322
3323                   if (tkind == 1 && strchr(star_match[0], ':')) {
3324                       /* Avoid "tells you:" spoofs in channels */
3325                      tkind = 3;
3326                   }
3327                   if (star_match[0][0] == NULLCHAR ||
3328                       strchr(star_match[0], ' ') ||
3329                       (tkind == 3 && strchr(star_match[1], ' '))) {
3330                     /* Reject bogus matches */
3331                     i = oldi;
3332                   } else {
3333                     if (appData.colorize) {
3334                       if (oldi > next_out) {
3335                         SendToPlayer(&buf[next_out], oldi - next_out);
3336                         next_out = oldi;
3337                       }
3338                       switch (tkind) {
3339                       case 1:
3340                         Colorize(ColorTell, FALSE);
3341                         curColor = ColorTell;
3342                         break;
3343                       case 2:
3344                         Colorize(ColorKibitz, FALSE);
3345                         curColor = ColorKibitz;
3346                         break;
3347                       case 3:
3348                         p = strrchr(star_match[1], '(');
3349                         if (p == NULL) {
3350                           p = star_match[1];
3351                         } else {
3352                           p++;
3353                         }
3354                         if (atoi(p) == 1) {
3355                           Colorize(ColorChannel1, FALSE);
3356                           curColor = ColorChannel1;
3357                         } else {
3358                           Colorize(ColorChannel, FALSE);
3359                           curColor = ColorChannel;
3360                         }
3361                         break;
3362                       case 5:
3363                         curColor = ColorNormal;
3364                         break;
3365                       }
3366                     }
3367                     if (started == STARTED_NONE && appData.autoComment &&
3368                         (gameMode == IcsObserving ||
3369                          gameMode == IcsPlayingWhite ||
3370                          gameMode == IcsPlayingBlack)) {
3371                       parse_pos = i - oldi;
3372                       memcpy(parse, &buf[oldi], parse_pos);
3373                       parse[parse_pos] = NULLCHAR;
3374                       started = STARTED_COMMENT;
3375                       savingComment = TRUE;
3376                     } else {
3377                       started = STARTED_CHATTER;
3378                       savingComment = FALSE;
3379                     }
3380                     loggedOn = TRUE;
3381                     continue;
3382                   }
3383                 }
3384
3385                 if (looking_at(buf, &i, "* s-shouts: ") ||
3386                     looking_at(buf, &i, "* c-shouts: ")) {
3387                     if (appData.colorize) {
3388                         if (oldi > next_out) {
3389                             SendToPlayer(&buf[next_out], oldi - next_out);
3390                             next_out = oldi;
3391                         }
3392                         Colorize(ColorSShout, FALSE);
3393                         curColor = ColorSShout;
3394                     }
3395                     loggedOn = TRUE;
3396                     started = STARTED_CHATTER;
3397                     continue;
3398                 }
3399
3400                 if (looking_at(buf, &i, "--->")) {
3401                     loggedOn = TRUE;
3402                     continue;
3403                 }
3404
3405                 if (looking_at(buf, &i, "* shouts: ") ||
3406                     looking_at(buf, &i, "--> ")) {
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorShout, FALSE);
3413                         curColor = ColorShout;
3414                     }
3415                     loggedOn = TRUE;
3416                     started = STARTED_CHATTER;
3417                     continue;
3418                 }
3419
3420                 if (looking_at( buf, &i, "Challenge:")) {
3421                     if (appData.colorize) {
3422                         if (oldi > next_out) {
3423                             SendToPlayer(&buf[next_out], oldi - next_out);
3424                             next_out = oldi;
3425                         }
3426                         Colorize(ColorChallenge, FALSE);
3427                         curColor = ColorChallenge;
3428                     }
3429                     loggedOn = TRUE;
3430                     continue;
3431                 }
3432
3433                 if (looking_at(buf, &i, "* offers you") ||
3434                     looking_at(buf, &i, "* offers to be") ||
3435                     looking_at(buf, &i, "* would like to") ||
3436                     looking_at(buf, &i, "* requests to") ||
3437                     looking_at(buf, &i, "Your opponent offers") ||
3438                     looking_at(buf, &i, "Your opponent requests")) {
3439
3440                     if (appData.colorize) {
3441                         if (oldi > next_out) {
3442                             SendToPlayer(&buf[next_out], oldi - next_out);
3443                             next_out = oldi;
3444                         }
3445                         Colorize(ColorRequest, FALSE);
3446                         curColor = ColorRequest;
3447                     }
3448                     continue;
3449                 }
3450
3451                 if (looking_at(buf, &i, "* (*) seeking")) {
3452                     if (appData.colorize) {
3453                         if (oldi > next_out) {
3454                             SendToPlayer(&buf[next_out], oldi - next_out);
3455                             next_out = oldi;
3456                         }
3457                         Colorize(ColorSeek, FALSE);
3458                         curColor = ColorSeek;
3459                     }
3460                     continue;
3461             }
3462
3463           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3464
3465             if (looking_at(buf, &i, "\\   ")) {
3466                 if (prevColor != ColorNormal) {
3467                     if (oldi > next_out) {
3468                         SendToPlayer(&buf[next_out], oldi - next_out);
3469                         next_out = oldi;
3470                     }
3471                     Colorize(prevColor, TRUE);
3472                     curColor = prevColor;
3473                 }
3474                 if (savingComment) {
3475                     parse_pos = i - oldi;
3476                     memcpy(parse, &buf[oldi], parse_pos);
3477                     parse[parse_pos] = NULLCHAR;
3478                     started = STARTED_COMMENT;
3479                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3480                         chattingPartner = savingComment - 3; // kludge to remember the box
3481                 } else {
3482                     started = STARTED_CHATTER;
3483                 }
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i, "Black Strength :") ||
3488                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3489                 looking_at(buf, &i, "<10>") ||
3490                 looking_at(buf, &i, "#@#")) {
3491                 /* Wrong board style */
3492                 loggedOn = TRUE;
3493                 SendToICS(ics_prefix);
3494                 SendToICS("set style 12\n");
3495                 SendToICS(ics_prefix);
3496                 SendToICS("refresh\n");
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "login:")) {
3501               if (!have_sent_ICS_logon) {
3502                 if(ICSInitScript())
3503                   have_sent_ICS_logon = 1;
3504                 else // no init script was found
3505                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3506               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3507                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3508               }
3509                 continue;
3510             }
3511
3512             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3513                 (looking_at(buf, &i, "\n<12> ") ||
3514                  looking_at(buf, &i, "<12> "))) {
3515                 loggedOn = TRUE;
3516                 if (oldi > next_out) {
3517                     SendToPlayer(&buf[next_out], oldi - next_out);
3518                 }
3519                 next_out = i;
3520                 started = STARTED_BOARD;
3521                 parse_pos = 0;
3522                 continue;
3523             }
3524
3525             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3526                 looking_at(buf, &i, "<b1> ")) {
3527                 if (oldi > next_out) {
3528                     SendToPlayer(&buf[next_out], oldi - next_out);
3529                 }
3530                 next_out = i;
3531                 started = STARTED_HOLDINGS;
3532                 parse_pos = 0;
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3537                 loggedOn = TRUE;
3538                 /* Header for a move list -- first line */
3539
3540                 switch (ics_getting_history) {
3541                   case H_FALSE:
3542                     switch (gameMode) {
3543                       case IcsIdle:
3544                       case BeginningOfGame:
3545                         /* User typed "moves" or "oldmoves" while we
3546                            were idle.  Pretend we asked for these
3547                            moves and soak them up so user can step
3548                            through them and/or save them.
3549                            */
3550                         Reset(FALSE, TRUE);
3551                         gameMode = IcsObserving;
3552                         ModeHighlight();
3553                         ics_gamenum = -1;
3554                         ics_getting_history = H_GOT_UNREQ_HEADER;
3555                         break;
3556                       case EditGame: /*?*/
3557                       case EditPosition: /*?*/
3558                         /* Should above feature work in these modes too? */
3559                         /* For now it doesn't */
3560                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3561                         break;
3562                       default:
3563                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3564                         break;
3565                     }
3566                     break;
3567                   case H_REQUESTED:
3568                     /* Is this the right one? */
3569                     if (gameInfo.white && gameInfo.black &&
3570                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3571                         strcmp(gameInfo.black, star_match[2]) == 0) {
3572                         /* All is well */
3573                         ics_getting_history = H_GOT_REQ_HEADER;
3574                     }
3575                     break;
3576                   case H_GOT_REQ_HEADER:
3577                   case H_GOT_UNREQ_HEADER:
3578                   case H_GOT_UNWANTED_HEADER:
3579                   case H_GETTING_MOVES:
3580                     /* Should not happen */
3581                     DisplayError(_("Error gathering move list: two headers"), 0);
3582                     ics_getting_history = H_FALSE;
3583                     break;
3584                 }
3585
3586                 /* Save player ratings into gameInfo if needed */
3587                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3588                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3589                     (gameInfo.whiteRating == -1 ||
3590                      gameInfo.blackRating == -1)) {
3591
3592                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3593                     gameInfo.blackRating = string_to_rating(star_match[3]);
3594                     if (appData.debugMode)
3595                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3596                               gameInfo.whiteRating, gameInfo.blackRating);
3597                 }
3598                 continue;
3599             }
3600
3601             if (looking_at(buf, &i,
3602               "* * match, initial time: * minute*, increment: * second")) {
3603                 /* Header for a move list -- second line */
3604                 /* Initial board will follow if this is a wild game */
3605                 if (gameInfo.event != NULL) free(gameInfo.event);
3606                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3607                 gameInfo.event = StrSave(str);
3608                 /* [HGM] we switched variant. Translate boards if needed. */
3609                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3610                 continue;
3611             }
3612
3613             if (looking_at(buf, &i, "Move  ")) {
3614                 /* Beginning of a move list */
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     /* Normally should not happen */
3618                     /* Maybe user hit reset while we were parsing */
3619                     break;
3620                   case H_REQUESTED:
3621                     /* Happens if we are ignoring a move list that is not
3622                      * the one we just requested.  Common if the user
3623                      * tries to observe two games without turning off
3624                      * getMoveList */
3625                     break;
3626                   case H_GETTING_MOVES:
3627                     /* Should not happen */
3628                     DisplayError(_("Error gathering move list: nested"), 0);
3629                     ics_getting_history = H_FALSE;
3630                     break;
3631                   case H_GOT_REQ_HEADER:
3632                     ics_getting_history = H_GETTING_MOVES;
3633                     started = STARTED_MOVES;
3634                     parse_pos = 0;
3635                     if (oldi > next_out) {
3636                         SendToPlayer(&buf[next_out], oldi - next_out);
3637                     }
3638                     break;
3639                   case H_GOT_UNREQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES_NOHIDE;
3642                     parse_pos = 0;
3643                     break;
3644                   case H_GOT_UNWANTED_HEADER:
3645                     ics_getting_history = H_FALSE;
3646                     break;
3647                 }
3648                 continue;
3649             }
3650
3651             if (looking_at(buf, &i, "% ") ||
3652                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3653                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3654                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3655                     soughtPending = FALSE;
3656                     seekGraphUp = TRUE;
3657                     DrawSeekGraph();
3658                 }
3659                 if(suppressKibitz) next_out = i;
3660                 savingComment = FALSE;
3661                 suppressKibitz = 0;
3662                 switch (started) {
3663                   case STARTED_MOVES:
3664                   case STARTED_MOVES_NOHIDE:
3665                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3666                     parse[parse_pos + i - oldi] = NULLCHAR;
3667                     ParseGameHistory(parse);
3668 #if ZIPPY
3669                     if (appData.zippyPlay && first.initDone) {
3670                         FeedMovesToProgram(&first, forwardMostMove);
3671                         if (gameMode == IcsPlayingWhite) {
3672                             if (WhiteOnMove(forwardMostMove)) {
3673                                 if (first.sendTime) {
3674                                   if (first.useColors) {
3675                                     SendToProgram("black\n", &first);
3676                                   }
3677                                   SendTimeRemaining(&first, TRUE);
3678                                 }
3679                                 if (first.useColors) {
3680                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3681                                 }
3682                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3683                                 first.maybeThinking = TRUE;
3684                             } else {
3685                                 if (first.usePlayother) {
3686                                   if (first.sendTime) {
3687                                     SendTimeRemaining(&first, TRUE);
3688                                   }
3689                                   SendToProgram("playother\n", &first);
3690                                   firstMove = FALSE;
3691                                 } else {
3692                                   firstMove = TRUE;
3693                                 }
3694                             }
3695                         } else if (gameMode == IcsPlayingBlack) {
3696                             if (!WhiteOnMove(forwardMostMove)) {
3697                                 if (first.sendTime) {
3698                                   if (first.useColors) {
3699                                     SendToProgram("white\n", &first);
3700                                   }
3701                                   SendTimeRemaining(&first, FALSE);
3702                                 }
3703                                 if (first.useColors) {
3704                                   SendToProgram("black\n", &first);
3705                                 }
3706                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3707                                 first.maybeThinking = TRUE;
3708                             } else {
3709                                 if (first.usePlayother) {
3710                                   if (first.sendTime) {
3711                                     SendTimeRemaining(&first, FALSE);
3712                                   }
3713                                   SendToProgram("playother\n", &first);
3714                                   firstMove = FALSE;
3715                                 } else {
3716                                   firstMove = TRUE;
3717                                 }
3718                             }
3719                         }
3720                     }
3721 #endif
3722                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3723                         /* Moves came from oldmoves or moves command
3724                            while we weren't doing anything else.
3725                            */
3726                         currentMove = forwardMostMove;
3727                         ClearHighlights();/*!!could figure this out*/
3728                         flipView = appData.flipView;
3729                         DrawPosition(TRUE, boards[currentMove]);
3730                         DisplayBothClocks();
3731                         snprintf(str, MSG_SIZ, "%s %s %s",
3732                                 gameInfo.white, _("vs."),  gameInfo.black);
3733                         DisplayTitle(str);
3734                         gameMode = IcsIdle;
3735                     } else {
3736                         /* Moves were history of an active game */
3737                         if (gameInfo.resultDetails != NULL) {
3738                             free(gameInfo.resultDetails);
3739                             gameInfo.resultDetails = NULL;
3740                         }
3741                     }
3742                     HistorySet(parseList, backwardMostMove,
3743                                forwardMostMove, currentMove-1);
3744                     DisplayMove(currentMove - 1);
3745                     if (started == STARTED_MOVES) next_out = i;
3746                     started = STARTED_NONE;
3747                     ics_getting_history = H_FALSE;
3748                     break;
3749
3750                   case STARTED_OBSERVE:
3751                     started = STARTED_NONE;
3752                     SendToICS(ics_prefix);
3753                     SendToICS("refresh\n");
3754                     break;
3755
3756                   default:
3757                     break;
3758                 }
3759                 if(bookHit) { // [HGM] book: simulate book reply
3760                     static char bookMove[MSG_SIZ]; // a bit generous?
3761
3762                     programStats.nodes = programStats.depth = programStats.time =
3763                     programStats.score = programStats.got_only_move = 0;
3764                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3765
3766                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3767                     strcat(bookMove, bookHit);
3768                     HandleMachineMove(bookMove, &first);
3769                 }
3770                 continue;
3771             }
3772
3773             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3774                  started == STARTED_HOLDINGS ||
3775                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3776                 /* Accumulate characters in move list or board */
3777                 parse[parse_pos++] = buf[i];
3778             }
3779
3780             /* Start of game messages.  Mostly we detect start of game
3781                when the first board image arrives.  On some versions
3782                of the ICS, though, we need to do a "refresh" after starting
3783                to observe in order to get the current board right away. */
3784             if (looking_at(buf, &i, "Adding game * to observation list")) {
3785                 started = STARTED_OBSERVE;
3786                 continue;
3787             }
3788
3789             /* Handle auto-observe */
3790             if (appData.autoObserve &&
3791                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3792                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3793                 char *player;
3794                 /* Choose the player that was highlighted, if any. */
3795                 if (star_match[0][0] == '\033' ||
3796                     star_match[1][0] != '\033') {
3797                     player = star_match[0];
3798                 } else {
3799                     player = star_match[2];
3800                 }
3801                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3802                         ics_prefix, StripHighlightAndTitle(player));
3803                 SendToICS(str);
3804
3805                 /* Save ratings from notify string */
3806                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3807                 player1Rating = string_to_rating(star_match[1]);
3808                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3809                 player2Rating = string_to_rating(star_match[3]);
3810
3811                 if (appData.debugMode)
3812                   fprintf(debugFP,
3813                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3814                           player1Name, player1Rating,
3815                           player2Name, player2Rating);
3816
3817                 continue;
3818             }
3819
3820             /* Deal with automatic examine mode after a game,
3821                and with IcsObserving -> IcsExamining transition */
3822             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3823                 looking_at(buf, &i, "has made you an examiner of game *")) {
3824
3825                 int gamenum = atoi(star_match[0]);
3826                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3827                     gamenum == ics_gamenum) {
3828                     /* We were already playing or observing this game;
3829                        no need to refetch history */
3830                     gameMode = IcsExamining;
3831                     if (pausing) {
3832                         pauseExamForwardMostMove = forwardMostMove;
3833                     } else if (currentMove < forwardMostMove) {
3834                         ForwardInner(forwardMostMove);
3835                     }
3836                 } else {
3837                     /* I don't think this case really can happen */
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                 }
3841                 continue;
3842             }
3843
3844             /* Error messages */
3845 //          if (ics_user_moved) {
3846             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3847                 if (looking_at(buf, &i, "Illegal move") ||
3848                     looking_at(buf, &i, "Not a legal move") ||
3849                     looking_at(buf, &i, "Your king is in check") ||
3850                     looking_at(buf, &i, "It isn't your turn") ||
3851                     looking_at(buf, &i, "It is not your move")) {
3852                     /* Illegal move */
3853                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3854                         currentMove = forwardMostMove-1;
3855                         DisplayMove(currentMove - 1); /* before DMError */
3856                         DrawPosition(FALSE, boards[currentMove]);
3857                         SwitchClocks(forwardMostMove-1); // [HGM] race
3858                         DisplayBothClocks();
3859                     }
3860                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3861                     ics_user_moved = 0;
3862                     continue;
3863                 }
3864             }
3865
3866             if (looking_at(buf, &i, "still have time") ||
3867                 looking_at(buf, &i, "not out of time") ||
3868                 looking_at(buf, &i, "either player is out of time") ||
3869                 looking_at(buf, &i, "has timeseal; checking")) {
3870                 /* We must have called his flag a little too soon */
3871                 whiteFlag = blackFlag = FALSE;
3872                 continue;
3873             }
3874
3875             if (looking_at(buf, &i, "added * seconds to") ||
3876                 looking_at(buf, &i, "seconds were added to")) {
3877                 /* Update the clocks */
3878                 SendToICS(ics_prefix);
3879                 SendToICS("refresh\n");
3880                 continue;
3881             }
3882
3883             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3884                 ics_clock_paused = TRUE;
3885                 StopClocks();
3886                 continue;
3887             }
3888
3889             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3890                 ics_clock_paused = FALSE;
3891                 StartClocks();
3892                 continue;
3893             }
3894
3895             /* Grab player ratings from the Creating: message.
3896                Note we have to check for the special case when
3897                the ICS inserts things like [white] or [black]. */
3898             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3899                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3900                 /* star_matches:
3901                    0    player 1 name (not necessarily white)
3902                    1    player 1 rating
3903                    2    empty, white, or black (IGNORED)
3904                    3    player 2 name (not necessarily black)
3905                    4    player 2 rating
3906
3907                    The names/ratings are sorted out when the game
3908                    actually starts (below).
3909                 */
3910                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3911                 player1Rating = string_to_rating(star_match[1]);
3912                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3913                 player2Rating = string_to_rating(star_match[4]);
3914
3915                 if (appData.debugMode)
3916                   fprintf(debugFP,
3917                           "Ratings from 'Creating:' %s %d, %s %d\n",
3918                           player1Name, player1Rating,
3919                           player2Name, player2Rating);
3920
3921                 continue;
3922             }
3923
3924             /* Improved generic start/end-of-game messages */
3925             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3926                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3927                 /* If tkind == 0: */
3928                 /* star_match[0] is the game number */
3929                 /*           [1] is the white player's name */
3930                 /*           [2] is the black player's name */
3931                 /* For end-of-game: */
3932                 /*           [3] is the reason for the game end */
3933                 /*           [4] is a PGN end game-token, preceded by " " */
3934                 /* For start-of-game: */
3935                 /*           [3] begins with "Creating" or "Continuing" */
3936                 /*           [4] is " *" or empty (don't care). */
3937                 int gamenum = atoi(star_match[0]);
3938                 char *whitename, *blackname, *why, *endtoken;
3939                 ChessMove endtype = EndOfFile;
3940
3941                 if (tkind == 0) {
3942                   whitename = star_match[1];
3943                   blackname = star_match[2];
3944                   why = star_match[3];
3945                   endtoken = star_match[4];
3946                 } else {
3947                   whitename = star_match[1];
3948                   blackname = star_match[3];
3949                   why = star_match[5];
3950                   endtoken = star_match[6];
3951                 }
3952
3953                 /* Game start messages */
3954                 if (strncmp(why, "Creating ", 9) == 0 ||
3955                     strncmp(why, "Continuing ", 11) == 0) {
3956                     gs_gamenum = gamenum;
3957                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3958                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3959                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3960 #if ZIPPY
3961                     if (appData.zippyPlay) {
3962                         ZippyGameStart(whitename, blackname);
3963                     }
3964 #endif /*ZIPPY*/
3965                     partnerBoardValid = FALSE; // [HGM] bughouse
3966                     continue;
3967                 }
3968
3969                 /* Game end messages */
3970                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3971                     ics_gamenum != gamenum) {
3972                     continue;
3973                 }
3974                 while (endtoken[0] == ' ') endtoken++;
3975                 switch (endtoken[0]) {
3976                   case '*':
3977                   default:
3978                     endtype = GameUnfinished;
3979                     break;
3980                   case '0':
3981                     endtype = BlackWins;
3982                     break;
3983                   case '1':
3984                     if (endtoken[1] == '/')
3985                       endtype = GameIsDrawn;
3986                     else
3987                       endtype = WhiteWins;
3988                     break;
3989                 }
3990                 GameEnds(endtype, why, GE_ICS);
3991 #if ZIPPY
3992                 if (appData.zippyPlay && first.initDone) {
3993                     ZippyGameEnd(endtype, why);
3994                     if (first.pr == NoProc) {
3995                       /* Start the next process early so that we'll
3996                          be ready for the next challenge */
3997                       StartChessProgram(&first);
3998                     }
3999                     /* Send "new" early, in case this command takes
4000                        a long time to finish, so that we'll be ready
4001                        for the next challenge. */
4002                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4003                     Reset(TRUE, TRUE);
4004                 }
4005 #endif /*ZIPPY*/
4006                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4007                 continue;
4008             }
4009
4010             if (looking_at(buf, &i, "Removing game * from observation") ||
4011                 looking_at(buf, &i, "no longer observing game *") ||
4012                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4013                 if (gameMode == IcsObserving &&
4014                     atoi(star_match[0]) == ics_gamenum)
4015                   {
4016                       /* icsEngineAnalyze */
4017                       if (appData.icsEngineAnalyze) {
4018                             ExitAnalyzeMode();
4019                             ModeHighlight();
4020                       }
4021                       StopClocks();
4022                       gameMode = IcsIdle;
4023                       ics_gamenum = -1;
4024                       ics_user_moved = FALSE;
4025                   }
4026                 continue;
4027             }
4028
4029             if (looking_at(buf, &i, "no longer examining game *")) {
4030                 if (gameMode == IcsExamining &&
4031                     atoi(star_match[0]) == ics_gamenum)
4032                   {
4033                       gameMode = IcsIdle;
4034                       ics_gamenum = -1;
4035                       ics_user_moved = FALSE;
4036                   }
4037                 continue;
4038             }
4039
4040             /* Advance leftover_start past any newlines we find,
4041                so only partial lines can get reparsed */
4042             if (looking_at(buf, &i, "\n")) {
4043                 prevColor = curColor;
4044                 if (curColor != ColorNormal) {
4045                     if (oldi > next_out) {
4046                         SendToPlayer(&buf[next_out], oldi - next_out);
4047                         next_out = oldi;
4048                     }
4049                     Colorize(ColorNormal, FALSE);
4050                     curColor = ColorNormal;
4051                 }
4052                 if (started == STARTED_BOARD) {
4053                     started = STARTED_NONE;
4054                     parse[parse_pos] = NULLCHAR;
4055                     ParseBoard12(parse);
4056                     ics_user_moved = 0;
4057
4058                     /* Send premove here */
4059                     if (appData.premove) {
4060                       char str[MSG_SIZ];
4061                       if (currentMove == 0 &&
4062                           gameMode == IcsPlayingWhite &&
4063                           appData.premoveWhite) {
4064                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4065                         if (appData.debugMode)
4066                           fprintf(debugFP, "Sending premove:\n");
4067                         SendToICS(str);
4068                       } else if (currentMove == 1 &&
4069                                  gameMode == IcsPlayingBlack &&
4070                                  appData.premoveBlack) {
4071                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4072                         if (appData.debugMode)
4073                           fprintf(debugFP, "Sending premove:\n");
4074                         SendToICS(str);
4075                       } else if (gotPremove) {
4076                         gotPremove = 0;
4077                         ClearPremoveHighlights();
4078                         if (appData.debugMode)
4079                           fprintf(debugFP, "Sending premove:\n");
4080                           UserMoveEvent(premoveFromX, premoveFromY,
4081                                         premoveToX, premoveToY,
4082                                         premovePromoChar);
4083                       }
4084                     }
4085
4086                     /* Usually suppress following prompt */
4087                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4088                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4089                         if (looking_at(buf, &i, "*% ")) {
4090                             savingComment = FALSE;
4091                             suppressKibitz = 0;
4092                         }
4093                     }
4094                     next_out = i;
4095                 } else if (started == STARTED_HOLDINGS) {
4096                     int gamenum;
4097                     char new_piece[MSG_SIZ];
4098                     started = STARTED_NONE;
4099                     parse[parse_pos] = NULLCHAR;
4100                     if (appData.debugMode)
4101                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4102                                                         parse, currentMove);
4103                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4104                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4105                         if (gameInfo.variant == VariantNormal) {
4106                           /* [HGM] We seem to switch variant during a game!
4107                            * Presumably no holdings were displayed, so we have
4108                            * to move the position two files to the right to
4109                            * create room for them!
4110                            */
4111                           VariantClass newVariant;
4112                           switch(gameInfo.boardWidth) { // base guess on board width
4113                                 case 9:  newVariant = VariantShogi; break;
4114                                 case 10: newVariant = VariantGreat; break;
4115                                 default: newVariant = VariantCrazyhouse; break;
4116                           }
4117                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4118                           /* Get a move list just to see the header, which
4119                              will tell us whether this is really bug or zh */
4120                           if (ics_getting_history == H_FALSE) {
4121                             ics_getting_history = H_REQUESTED;
4122                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4123                             SendToICS(str);
4124                           }
4125                         }
4126                         new_piece[0] = NULLCHAR;
4127                         sscanf(parse, "game %d white [%s black [%s <- %s",
4128                                &gamenum, white_holding, black_holding,
4129                                new_piece);
4130                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4131                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4132                         /* [HGM] copy holdings to board holdings area */
4133                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4134                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4135                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4136 #if ZIPPY
4137                         if (appData.zippyPlay && first.initDone) {
4138                             ZippyHoldings(white_holding, black_holding,
4139                                           new_piece);
4140                         }
4141 #endif /*ZIPPY*/
4142                         if (tinyLayout || smallLayout) {
4143                             char wh[16], bh[16];
4144                             PackHolding(wh, white_holding);
4145                             PackHolding(bh, black_holding);
4146                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4147                                     gameInfo.white, gameInfo.black);
4148                         } else {
4149                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4150                                     gameInfo.white, white_holding, _("vs."),
4151                                     gameInfo.black, black_holding);
4152                         }
4153                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4154                         DrawPosition(FALSE, boards[currentMove]);
4155                         DisplayTitle(str);
4156                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4157                         sscanf(parse, "game %d white [%s black [%s <- %s",
4158                                &gamenum, white_holding, black_holding,
4159                                new_piece);
4160                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4161                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4162                         /* [HGM] copy holdings to partner-board holdings area */
4163                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4164                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4165                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4166                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4167                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4168                       }
4169                     }
4170                     /* Suppress following prompt */
4171                     if (looking_at(buf, &i, "*% ")) {
4172                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4173                         savingComment = FALSE;
4174                         suppressKibitz = 0;
4175                     }
4176                     next_out = i;
4177                 }
4178                 continue;
4179             }
4180
4181             i++;                /* skip unparsed character and loop back */
4182         }
4183
4184         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4185 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4186 //          SendToPlayer(&buf[next_out], i - next_out);
4187             started != STARTED_HOLDINGS && leftover_start > next_out) {
4188             SendToPlayer(&buf[next_out], leftover_start - next_out);
4189             next_out = i;
4190         }
4191
4192         leftover_len = buf_len - leftover_start;
4193         /* if buffer ends with something we couldn't parse,
4194            reparse it after appending the next read */
4195
4196     } else if (count == 0) {
4197         RemoveInputSource(isr);
4198         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4199     } else {
4200         DisplayFatalError(_("Error reading from ICS"), error, 1);
4201     }
4202 }
4203
4204
4205 /* Board style 12 looks like this:
4206
4207    <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
4208
4209  * The "<12> " is stripped before it gets to this routine.  The two
4210  * trailing 0's (flip state and clock ticking) are later addition, and
4211  * some chess servers may not have them, or may have only the first.
4212  * Additional trailing fields may be added in the future.
4213  */
4214
4215 #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"
4216
4217 #define RELATION_OBSERVING_PLAYED    0
4218 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4219 #define RELATION_PLAYING_MYMOVE      1
4220 #define RELATION_PLAYING_NOTMYMOVE  -1
4221 #define RELATION_EXAMINING           2
4222 #define RELATION_ISOLATED_BOARD     -3
4223 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4224
4225 void
4226 ParseBoard12 (char *string)
4227 {
4228 #if ZIPPY
4229     int i, takeback;
4230     char *bookHit = NULL; // [HGM] book
4231 #endif
4232     GameMode newGameMode;
4233     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4234     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4235     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4236     char to_play, board_chars[200];
4237     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4238     char black[32], white[32];
4239     Board board;
4240     int prevMove = currentMove;
4241     int ticking = 2;
4242     ChessMove moveType;
4243     int fromX, fromY, toX, toY;
4244     char promoChar;
4245     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4246     Boolean weird = FALSE, reqFlag = FALSE;
4247
4248     fromX = fromY = toX = toY = -1;
4249
4250     newGame = FALSE;
4251
4252     if (appData.debugMode)
4253       fprintf(debugFP, "Parsing board: %s\n", string);
4254
4255     move_str[0] = NULLCHAR;
4256     elapsed_time[0] = NULLCHAR;
4257     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4258         int  i = 0, j;
4259         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4260             if(string[i] == ' ') { ranks++; files = 0; }
4261             else files++;
4262             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4263             i++;
4264         }
4265         for(j = 0; j <i; j++) board_chars[j] = string[j];
4266         board_chars[i] = '\0';
4267         string += i + 1;
4268     }
4269     n = sscanf(string, PATTERN, &to_play, &double_push,
4270                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4271                &gamenum, white, black, &relation, &basetime, &increment,
4272                &white_stren, &black_stren, &white_time, &black_time,
4273                &moveNum, str, elapsed_time, move_str, &ics_flip,
4274                &ticking);
4275
4276     if (n < 21) {
4277         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4278         DisplayError(str, 0);
4279         return;
4280     }
4281
4282     /* Convert the move number to internal form */
4283     moveNum = (moveNum - 1) * 2;
4284     if (to_play == 'B') moveNum++;
4285     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4286       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4287                         0, 1);
4288       return;
4289     }
4290
4291     switch (relation) {
4292       case RELATION_OBSERVING_PLAYED:
4293       case RELATION_OBSERVING_STATIC:
4294         if (gamenum == -1) {
4295             /* Old ICC buglet */
4296             relation = RELATION_OBSERVING_STATIC;
4297         }
4298         newGameMode = IcsObserving;
4299         break;
4300       case RELATION_PLAYING_MYMOVE:
4301       case RELATION_PLAYING_NOTMYMOVE:
4302         newGameMode =
4303           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4304             IcsPlayingWhite : IcsPlayingBlack;
4305         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4306         break;
4307       case RELATION_EXAMINING:
4308         newGameMode = IcsExamining;
4309         break;
4310       case RELATION_ISOLATED_BOARD:
4311       default:
4312         /* Just display this board.  If user was doing something else,
4313            we will forget about it until the next board comes. */
4314         newGameMode = IcsIdle;
4315         break;
4316       case RELATION_STARTING_POSITION:
4317         newGameMode = gameMode;
4318         break;
4319     }
4320
4321     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4322         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4323          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4324       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4325       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4326       static int lastBgGame = -1;
4327       char *toSqr;
4328       for (k = 0; k < ranks; k++) {
4329         for (j = 0; j < files; j++)
4330           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4331         if(gameInfo.holdingsWidth > 1) {
4332              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4333              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4334         }
4335       }
4336       CopyBoard(partnerBoard, board);
4337       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4338         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4339         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4340       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4341       if(toSqr = strchr(str, '-')) {
4342         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4343         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4344       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4345       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4346       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4347       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4348       if(twoBoards) {
4349           DisplayWhiteClock(white_time*fac, to_play == 'W');
4350           DisplayBlackClock(black_time*fac, to_play != 'W');
4351           activePartner = to_play;
4352           if(gamenum != lastBgGame) {
4353               char buf[MSG_SIZ];
4354               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4355               DisplayTitle(buf);
4356           }
4357           lastBgGame = gamenum;
4358           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4359                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4360       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4361                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4362       if(!twoBoards) DisplayMessage(partnerStatus, "");
4363         partnerBoardValid = TRUE;
4364       return;
4365     }
4366
4367     if(appData.dualBoard && appData.bgObserve) {
4368         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4369             SendToICS(ics_prefix), SendToICS("pobserve\n");
4370         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4371             char buf[MSG_SIZ];
4372             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4373             SendToICS(buf);
4374         }
4375     }
4376
4377     /* Modify behavior for initial board display on move listing
4378        of wild games.
4379        */
4380     switch (ics_getting_history) {
4381       case H_FALSE:
4382       case H_REQUESTED:
4383         break;
4384       case H_GOT_REQ_HEADER:
4385       case H_GOT_UNREQ_HEADER:
4386         /* This is the initial position of the current game */
4387         gamenum = ics_gamenum;
4388         moveNum = 0;            /* old ICS bug workaround */
4389         if (to_play == 'B') {
4390           startedFromSetupPosition = TRUE;
4391           blackPlaysFirst = TRUE;
4392           moveNum = 1;
4393           if (forwardMostMove == 0) forwardMostMove = 1;
4394           if (backwardMostMove == 0) backwardMostMove = 1;
4395           if (currentMove == 0) currentMove = 1;
4396         }
4397         newGameMode = gameMode;
4398         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4399         break;
4400       case H_GOT_UNWANTED_HEADER:
4401         /* This is an initial board that we don't want */
4402         return;
4403       case H_GETTING_MOVES:
4404         /* Should not happen */
4405         DisplayError(_("Error gathering move list: extra board"), 0);
4406         ics_getting_history = H_FALSE;
4407         return;
4408     }
4409
4410    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4411                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4412                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4413      /* [HGM] We seem to have switched variant unexpectedly
4414       * Try to guess new variant from board size
4415       */
4416           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4417           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4418           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4419           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4420           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4421           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4422           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4423           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4424           /* Get a move list just to see the header, which
4425              will tell us whether this is really bug or zh */
4426           if (ics_getting_history == H_FALSE) {
4427             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4428             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4429             SendToICS(str);
4430           }
4431     }
4432
4433     /* Take action if this is the first board of a new game, or of a
4434        different game than is currently being displayed.  */
4435     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4436         relation == RELATION_ISOLATED_BOARD) {
4437
4438         /* Forget the old game and get the history (if any) of the new one */
4439         if (gameMode != BeginningOfGame) {
4440           Reset(TRUE, TRUE);
4441         }
4442         newGame = TRUE;
4443         if (appData.autoRaiseBoard) BoardToTop();
4444         prevMove = -3;
4445         if (gamenum == -1) {
4446             newGameMode = IcsIdle;
4447         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4448                    appData.getMoveList && !reqFlag) {
4449             /* Need to get game history */
4450             ics_getting_history = H_REQUESTED;
4451             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4452             SendToICS(str);
4453         }
4454
4455         /* Initially flip the board to have black on the bottom if playing
4456            black or if the ICS flip flag is set, but let the user change
4457            it with the Flip View button. */
4458         flipView = appData.autoFlipView ?
4459           (newGameMode == IcsPlayingBlack) || ics_flip :
4460           appData.flipView;
4461
4462         /* Done with values from previous mode; copy in new ones */
4463         gameMode = newGameMode;
4464         ModeHighlight();
4465         ics_gamenum = gamenum;
4466         if (gamenum == gs_gamenum) {
4467             int klen = strlen(gs_kind);
4468             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4469             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4470             gameInfo.event = StrSave(str);
4471         } else {
4472             gameInfo.event = StrSave("ICS game");
4473         }
4474         gameInfo.site = StrSave(appData.icsHost);
4475         gameInfo.date = PGNDate();
4476         gameInfo.round = StrSave("-");
4477         gameInfo.white = StrSave(white);
4478         gameInfo.black = StrSave(black);
4479         timeControl = basetime * 60 * 1000;
4480         timeControl_2 = 0;
4481         timeIncrement = increment * 1000;
4482         movesPerSession = 0;
4483         gameInfo.timeControl = TimeControlTagValue();
4484         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4485   if (appData.debugMode) {
4486     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4487     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4488     setbuf(debugFP, NULL);
4489   }
4490
4491         gameInfo.outOfBook = NULL;
4492
4493         /* Do we have the ratings? */
4494         if (strcmp(player1Name, white) == 0 &&
4495             strcmp(player2Name, black) == 0) {
4496             if (appData.debugMode)
4497               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4498                       player1Rating, player2Rating);
4499             gameInfo.whiteRating = player1Rating;
4500             gameInfo.blackRating = player2Rating;
4501         } else if (strcmp(player2Name, white) == 0 &&
4502                    strcmp(player1Name, black) == 0) {
4503             if (appData.debugMode)
4504               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4505                       player2Rating, player1Rating);
4506             gameInfo.whiteRating = player2Rating;
4507             gameInfo.blackRating = player1Rating;
4508         }
4509         player1Name[0] = player2Name[0] = NULLCHAR;
4510
4511         /* Silence shouts if requested */
4512         if (appData.quietPlay &&
4513             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4514             SendToICS(ics_prefix);
4515             SendToICS("set shout 0\n");
4516         }
4517     }
4518
4519     /* Deal with midgame name changes */
4520     if (!newGame) {
4521         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4522             if (gameInfo.white) free(gameInfo.white);
4523             gameInfo.white = StrSave(white);
4524         }
4525         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4526             if (gameInfo.black) free(gameInfo.black);
4527             gameInfo.black = StrSave(black);
4528         }
4529     }
4530
4531     /* Throw away game result if anything actually changes in examine mode */
4532     if (gameMode == IcsExamining && !newGame) {
4533         gameInfo.result = GameUnfinished;
4534         if (gameInfo.resultDetails != NULL) {
4535             free(gameInfo.resultDetails);
4536             gameInfo.resultDetails = NULL;
4537         }
4538     }
4539
4540     /* In pausing && IcsExamining mode, we ignore boards coming
4541        in if they are in a different variation than we are. */
4542     if (pauseExamInvalid) return;
4543     if (pausing && gameMode == IcsExamining) {
4544         if (moveNum <= pauseExamForwardMostMove) {
4545             pauseExamInvalid = TRUE;
4546             forwardMostMove = pauseExamForwardMostMove;
4547             return;
4548         }
4549     }
4550
4551   if (appData.debugMode) {
4552     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4553   }
4554     /* Parse the board */
4555     for (k = 0; k < ranks; k++) {
4556       for (j = 0; j < files; j++)
4557         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4558       if(gameInfo.holdingsWidth > 1) {
4559            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4560            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4561       }
4562     }
4563     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4564       board[5][BOARD_RGHT+1] = WhiteAngel;
4565       board[6][BOARD_RGHT+1] = WhiteMarshall;
4566       board[1][0] = BlackMarshall;
4567       board[2][0] = BlackAngel;
4568       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4569     }
4570     CopyBoard(boards[moveNum], board);
4571     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4572     if (moveNum == 0) {
4573         startedFromSetupPosition =
4574           !CompareBoards(board, initialPosition);
4575         if(startedFromSetupPosition)
4576             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4577     }
4578
4579     /* [HGM] Set castling rights. Take the outermost Rooks,
4580        to make it also work for FRC opening positions. Note that board12
4581        is really defective for later FRC positions, as it has no way to
4582        indicate which Rook can castle if they are on the same side of King.
4583        For the initial position we grant rights to the outermost Rooks,
4584        and remember thos rights, and we then copy them on positions
4585        later in an FRC game. This means WB might not recognize castlings with
4586        Rooks that have moved back to their original position as illegal,
4587        but in ICS mode that is not its job anyway.
4588     */
4589     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4590     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4591
4592         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4593             if(board[0][i] == WhiteRook) j = i;
4594         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4595         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4596             if(board[0][i] == WhiteRook) j = i;
4597         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4598         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4599             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4600         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4601         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4602             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4603         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4604
4605         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4606         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4607         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4608             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4609         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4610             if(board[BOARD_HEIGHT-1][k] == bKing)
4611                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4612         if(gameInfo.variant == VariantTwoKings) {
4613             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4614             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4615             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4616         }
4617     } else { int r;
4618         r = boards[moveNum][CASTLING][0] = initialRights[0];
4619         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4620         r = boards[moveNum][CASTLING][1] = initialRights[1];
4621         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4622         r = boards[moveNum][CASTLING][3] = initialRights[3];
4623         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4624         r = boards[moveNum][CASTLING][4] = initialRights[4];
4625         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4626         /* wildcastle kludge: always assume King has rights */
4627         r = boards[moveNum][CASTLING][2] = initialRights[2];
4628         r = boards[moveNum][CASTLING][5] = initialRights[5];
4629     }
4630     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4631     boards[moveNum][EP_STATUS] = EP_NONE;
4632     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4633     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4634     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4635
4636
4637     if (ics_getting_history == H_GOT_REQ_HEADER ||
4638         ics_getting_history == H_GOT_UNREQ_HEADER) {
4639         /* This was an initial position from a move list, not
4640            the current position */
4641         return;
4642     }
4643
4644     /* Update currentMove and known move number limits */
4645     newMove = newGame || moveNum > forwardMostMove;
4646
4647     if (newGame) {
4648         forwardMostMove = backwardMostMove = currentMove = moveNum;
4649         if (gameMode == IcsExamining && moveNum == 0) {
4650           /* Workaround for ICS limitation: we are not told the wild
4651              type when starting to examine a game.  But if we ask for
4652              the move list, the move list header will tell us */
4653             ics_getting_history = H_REQUESTED;
4654             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4655             SendToICS(str);
4656         }
4657     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4658                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4659 #if ZIPPY
4660         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4661         /* [HGM] applied this also to an engine that is silently watching        */
4662         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4663             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4664             gameInfo.variant == currentlyInitializedVariant) {
4665           takeback = forwardMostMove - moveNum;
4666           for (i = 0; i < takeback; i++) {
4667             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4668             SendToProgram("undo\n", &first);
4669           }
4670         }
4671 #endif
4672
4673         forwardMostMove = moveNum;
4674         if (!pausing || currentMove > forwardMostMove)
4675           currentMove = forwardMostMove;
4676     } else {
4677         /* New part of history that is not contiguous with old part */
4678         if (pausing && gameMode == IcsExamining) {
4679             pauseExamInvalid = TRUE;
4680             forwardMostMove = pauseExamForwardMostMove;
4681             return;
4682         }
4683         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4684 #if ZIPPY
4685             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4686                 // [HGM] when we will receive the move list we now request, it will be
4687                 // fed to the engine from the first move on. So if the engine is not
4688                 // in the initial position now, bring it there.
4689                 InitChessProgram(&first, 0);
4690             }
4691 #endif
4692             ics_getting_history = H_REQUESTED;
4693             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4694             SendToICS(str);
4695         }
4696         forwardMostMove = backwardMostMove = currentMove = moveNum;
4697     }
4698
4699     /* Update the clocks */
4700     if (strchr(elapsed_time, '.')) {
4701       /* Time is in ms */
4702       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4703       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4704     } else {
4705       /* Time is in seconds */
4706       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4707       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4708     }
4709
4710
4711 #if ZIPPY
4712     if (appData.zippyPlay && newGame &&
4713         gameMode != IcsObserving && gameMode != IcsIdle &&
4714         gameMode != IcsExamining)
4715       ZippyFirstBoard(moveNum, basetime, increment);
4716 #endif
4717
4718     /* Put the move on the move list, first converting
4719        to canonical algebraic form. */
4720     if (moveNum > 0) {
4721   if (appData.debugMode) {
4722     int f = forwardMostMove;
4723     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4724             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4725             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4726     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4727     fprintf(debugFP, "moveNum = %d\n", moveNum);
4728     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4729     setbuf(debugFP, NULL);
4730   }
4731         if (moveNum <= backwardMostMove) {
4732             /* We don't know what the board looked like before
4733                this move.  Punt. */
4734           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4735             strcat(parseList[moveNum - 1], " ");
4736             strcat(parseList[moveNum - 1], elapsed_time);
4737             moveList[moveNum - 1][0] = NULLCHAR;
4738         } else if (strcmp(move_str, "none") == 0) {
4739             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4740             /* Again, we don't know what the board looked like;
4741                this is really the start of the game. */
4742             parseList[moveNum - 1][0] = NULLCHAR;
4743             moveList[moveNum - 1][0] = NULLCHAR;
4744             backwardMostMove = moveNum;
4745             startedFromSetupPosition = TRUE;
4746             fromX = fromY = toX = toY = -1;
4747         } else {
4748           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4749           //                 So we parse the long-algebraic move string in stead of the SAN move
4750           int valid; char buf[MSG_SIZ], *prom;
4751
4752           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4753                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4754           // str looks something like "Q/a1-a2"; kill the slash
4755           if(str[1] == '/')
4756             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4757           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4758           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4759                 strcat(buf, prom); // long move lacks promo specification!
4760           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4761                 if(appData.debugMode)
4762                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4763                 safeStrCpy(move_str, buf, MSG_SIZ);
4764           }
4765           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4766                                 &fromX, &fromY, &toX, &toY, &promoChar)
4767                || ParseOneMove(buf, moveNum - 1, &moveType,
4768                                 &fromX, &fromY, &toX, &toY, &promoChar);
4769           // end of long SAN patch
4770           if (valid) {
4771             (void) CoordsToAlgebraic(boards[moveNum - 1],
4772                                      PosFlags(moveNum - 1),
4773                                      fromY, fromX, toY, toX, promoChar,
4774                                      parseList[moveNum-1]);
4775             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4776               case MT_NONE:
4777               case MT_STALEMATE:
4778               default:
4779                 break;
4780               case MT_CHECK:
4781                 if(gameInfo.variant != VariantShogi)
4782                     strcat(parseList[moveNum - 1], "+");
4783                 break;
4784               case MT_CHECKMATE:
4785               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4786                 strcat(parseList[moveNum - 1], "#");
4787                 break;
4788             }
4789             strcat(parseList[moveNum - 1], " ");
4790             strcat(parseList[moveNum - 1], elapsed_time);
4791             /* currentMoveString is set as a side-effect of ParseOneMove */
4792             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4793             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4794             strcat(moveList[moveNum - 1], "\n");
4795
4796             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4797                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4798               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4799                 ChessSquare old, new = boards[moveNum][k][j];
4800                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4801                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4802                   if(old == new) continue;
4803                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4804                   else if(new == WhiteWazir || new == BlackWazir) {
4805                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4806                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4807                       else boards[moveNum][k][j] = old; // preserve type of Gold
4808                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4809                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4810               }
4811           } else {
4812             /* Move from ICS was illegal!?  Punt. */
4813             if (appData.debugMode) {
4814               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4815               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4816             }
4817             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4818             strcat(parseList[moveNum - 1], " ");
4819             strcat(parseList[moveNum - 1], elapsed_time);
4820             moveList[moveNum - 1][0] = NULLCHAR;
4821             fromX = fromY = toX = toY = -1;
4822           }
4823         }
4824   if (appData.debugMode) {
4825     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4826     setbuf(debugFP, NULL);
4827   }
4828
4829 #if ZIPPY
4830         /* Send move to chess program (BEFORE animating it). */
4831         if (appData.zippyPlay && !newGame && newMove &&
4832            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4833
4834             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4835                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4836                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4837                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4838                             move_str);
4839                     DisplayError(str, 0);
4840                 } else {
4841                     if (first.sendTime) {
4842                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4843                     }
4844                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4845                     if (firstMove && !bookHit) {
4846                         firstMove = FALSE;
4847                         if (first.useColors) {
4848                           SendToProgram(gameMode == IcsPlayingWhite ?
4849                                         "white\ngo\n" :
4850                                         "black\ngo\n", &first);
4851                         } else {
4852                           SendToProgram("go\n", &first);
4853                         }
4854                         first.maybeThinking = TRUE;
4855                     }
4856                 }
4857             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4858               if (moveList[moveNum - 1][0] == NULLCHAR) {
4859                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4860                 DisplayError(str, 0);
4861               } else {
4862                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4863                 SendMoveToProgram(moveNum - 1, &first);
4864               }
4865             }
4866         }
4867 #endif
4868     }
4869
4870     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4871         /* If move comes from a remote source, animate it.  If it
4872            isn't remote, it will have already been animated. */
4873         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4874             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4875         }
4876         if (!pausing && appData.highlightLastMove) {
4877             SetHighlights(fromX, fromY, toX, toY);
4878         }
4879     }
4880
4881     /* Start the clocks */
4882     whiteFlag = blackFlag = FALSE;
4883     appData.clockMode = !(basetime == 0 && increment == 0);
4884     if (ticking == 0) {
4885       ics_clock_paused = TRUE;
4886       StopClocks();
4887     } else if (ticking == 1) {
4888       ics_clock_paused = FALSE;
4889     }
4890     if (gameMode == IcsIdle ||
4891         relation == RELATION_OBSERVING_STATIC ||
4892         relation == RELATION_EXAMINING ||
4893         ics_clock_paused)
4894       DisplayBothClocks();
4895     else
4896       StartClocks();
4897
4898     /* Display opponents and material strengths */
4899     if (gameInfo.variant != VariantBughouse &&
4900         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4901         if (tinyLayout || smallLayout) {
4902             if(gameInfo.variant == VariantNormal)
4903               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4904                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4905                     basetime, increment);
4906             else
4907               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4908                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4909                     basetime, increment, (int) gameInfo.variant);
4910         } else {
4911             if(gameInfo.variant == VariantNormal)
4912               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4913                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4914                     basetime, increment);
4915             else
4916               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4917                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4918                     basetime, increment, VariantName(gameInfo.variant));
4919         }
4920         DisplayTitle(str);
4921   if (appData.debugMode) {
4922     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4923   }
4924     }
4925
4926
4927     /* Display the board */
4928     if (!pausing && !appData.noGUI) {
4929
4930       if (appData.premove)
4931           if (!gotPremove ||
4932              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4933              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4934               ClearPremoveHighlights();
4935
4936       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4937         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4938       DrawPosition(j, boards[currentMove]);
4939
4940       DisplayMove(moveNum - 1);
4941       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4942             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4943               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4944         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4945       }
4946     }
4947
4948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4949 #if ZIPPY
4950     if(bookHit) { // [HGM] book: simulate book reply
4951         static char bookMove[MSG_SIZ]; // a bit generous?
4952
4953         programStats.nodes = programStats.depth = programStats.time =
4954         programStats.score = programStats.got_only_move = 0;
4955         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4956
4957         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4958         strcat(bookMove, bookHit);
4959         HandleMachineMove(bookMove, &first);
4960     }
4961 #endif
4962 }
4963
4964 void
4965 GetMoveListEvent ()
4966 {
4967     char buf[MSG_SIZ];
4968     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4969         ics_getting_history = H_REQUESTED;
4970         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4971         SendToICS(buf);
4972     }
4973 }
4974
4975 void
4976 SendToBoth (char *msg)
4977 {   // to make it easy to keep two engines in step in dual analysis
4978     SendToProgram(msg, &first);
4979     if(second.analyzing) SendToProgram(msg, &second);
4980 }
4981
4982 void
4983 AnalysisPeriodicEvent (int force)
4984 {
4985     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4986          && !force) || !appData.periodicUpdates)
4987       return;
4988
4989     /* Send . command to Crafty to collect stats */
4990     SendToBoth(".\n");
4991
4992     /* Don't send another until we get a response (this makes
4993        us stop sending to old Crafty's which don't understand
4994        the "." command (sending illegal cmds resets node count & time,
4995        which looks bad)) */
4996     programStats.ok_to_send = 0;
4997 }
4998
4999 void
5000 ics_update_width (int new_width)
5001 {
5002         ics_printf("set width %d\n", new_width);
5003 }
5004
5005 void
5006 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5007 {
5008     char buf[MSG_SIZ];
5009
5010     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5011         // null move in variant where engine does not understand it (for analysis purposes)
5012         SendBoard(cps, moveNum + 1); // send position after move in stead.
5013         return;
5014     }
5015     if (cps->useUsermove) {
5016       SendToProgram("usermove ", cps);
5017     }
5018     if (cps->useSAN) {
5019       char *space;
5020       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5021         int len = space - parseList[moveNum];
5022         memcpy(buf, parseList[moveNum], len);
5023         buf[len++] = '\n';
5024         buf[len] = NULLCHAR;
5025       } else {
5026         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5027       }
5028       SendToProgram(buf, cps);
5029     } else {
5030       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5031         AlphaRank(moveList[moveNum], 4);
5032         SendToProgram(moveList[moveNum], cps);
5033         AlphaRank(moveList[moveNum], 4); // and back
5034       } else
5035       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5036        * the engine. It would be nice to have a better way to identify castle
5037        * moves here. */
5038       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5039                                                                          && cps->useOOCastle) {
5040         int fromX = moveList[moveNum][0] - AAA;
5041         int fromY = moveList[moveNum][1] - ONE;
5042         int toX = moveList[moveNum][2] - AAA;
5043         int toY = moveList[moveNum][3] - ONE;
5044         if((boards[moveNum][fromY][fromX] == WhiteKing
5045             && boards[moveNum][toY][toX] == WhiteRook)
5046            || (boards[moveNum][fromY][fromX] == BlackKing
5047                && boards[moveNum][toY][toX] == BlackRook)) {
5048           if(toX > fromX) SendToProgram("O-O\n", cps);
5049           else SendToProgram("O-O-O\n", cps);
5050         }
5051         else SendToProgram(moveList[moveNum], cps);
5052       } else
5053       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5054         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5055           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5056           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5057                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5058         } else
5059           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5060                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5061         SendToProgram(buf, cps);
5062       }
5063       else SendToProgram(moveList[moveNum], cps);
5064       /* End of additions by Tord */
5065     }
5066
5067     /* [HGM] setting up the opening has brought engine in force mode! */
5068     /*       Send 'go' if we are in a mode where machine should play. */
5069     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5070         (gameMode == TwoMachinesPlay   ||
5071 #if ZIPPY
5072          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5073 #endif
5074          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5075         SendToProgram("go\n", cps);
5076   if (appData.debugMode) {
5077     fprintf(debugFP, "(extra)\n");
5078   }
5079     }
5080     setboardSpoiledMachineBlack = 0;
5081 }
5082
5083 void
5084 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5085 {
5086     char user_move[MSG_SIZ];
5087     char suffix[4];
5088
5089     if(gameInfo.variant == VariantSChess && promoChar) {
5090         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5091         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5092     } else suffix[0] = NULLCHAR;
5093
5094     switch (moveType) {
5095       default:
5096         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5097                 (int)moveType, fromX, fromY, toX, toY);
5098         DisplayError(user_move + strlen("say "), 0);
5099         break;
5100       case WhiteKingSideCastle:
5101       case BlackKingSideCastle:
5102       case WhiteQueenSideCastleWild:
5103       case BlackQueenSideCastleWild:
5104       /* PUSH Fabien */
5105       case WhiteHSideCastleFR:
5106       case BlackHSideCastleFR:
5107       /* POP Fabien */
5108         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5109         break;
5110       case WhiteQueenSideCastle:
5111       case BlackQueenSideCastle:
5112       case WhiteKingSideCastleWild:
5113       case BlackKingSideCastleWild:
5114       /* PUSH Fabien */
5115       case WhiteASideCastleFR:
5116       case BlackASideCastleFR:
5117       /* POP Fabien */
5118         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5119         break;
5120       case WhiteNonPromotion:
5121       case BlackNonPromotion:
5122         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5123         break;
5124       case WhitePromotion:
5125       case BlackPromotion:
5126         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5127            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5128           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5130                 PieceToChar(WhiteFerz));
5131         else if(gameInfo.variant == VariantGreat)
5132           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5133                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5134                 PieceToChar(WhiteMan));
5135         else
5136           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5137                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5138                 promoChar);
5139         break;
5140       case WhiteDrop:
5141       case BlackDrop:
5142       drop:
5143         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5144                  ToUpper(PieceToChar((ChessSquare) fromX)),
5145                  AAA + toX, ONE + toY);
5146         break;
5147       case IllegalMove:  /* could be a variant we don't quite understand */
5148         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5149       case NormalMove:
5150       case WhiteCapturesEnPassant:
5151       case BlackCapturesEnPassant:
5152         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5153                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5154         break;
5155     }
5156     SendToICS(user_move);
5157     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5158         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5159 }
5160
5161 void
5162 UploadGameEvent ()
5163 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5164     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5165     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5166     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5167       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5168       return;
5169     }
5170     if(gameMode != IcsExamining) { // is this ever not the case?
5171         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5172
5173         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5174           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5175         } else { // on FICS we must first go to general examine mode
5176           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5177         }
5178         if(gameInfo.variant != VariantNormal) {
5179             // try figure out wild number, as xboard names are not always valid on ICS
5180             for(i=1; i<=36; i++) {
5181               snprintf(buf, MSG_SIZ, "wild/%d", i);
5182                 if(StringToVariant(buf) == gameInfo.variant) break;
5183             }
5184             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5185             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5186             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5187         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5188         SendToICS(ics_prefix);
5189         SendToICS(buf);
5190         if(startedFromSetupPosition || backwardMostMove != 0) {
5191           fen = PositionToFEN(backwardMostMove, NULL, 1);
5192           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5193             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5194             SendToICS(buf);
5195           } else { // FICS: everything has to set by separate bsetup commands
5196             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5197             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5198             SendToICS(buf);
5199             if(!WhiteOnMove(backwardMostMove)) {
5200                 SendToICS("bsetup tomove black\n");
5201             }
5202             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5203             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5204             SendToICS(buf);
5205             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5206             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5207             SendToICS(buf);
5208             i = boards[backwardMostMove][EP_STATUS];
5209             if(i >= 0) { // set e.p.
5210               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5211                 SendToICS(buf);
5212             }
5213             bsetup++;
5214           }
5215         }
5216       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5217             SendToICS("bsetup done\n"); // switch to normal examining.
5218     }
5219     for(i = backwardMostMove; i<last; i++) {
5220         char buf[20];
5221         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5222         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5223             int len = strlen(moveList[i]);
5224             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5225             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5226         }
5227         SendToICS(buf);
5228     }
5229     SendToICS(ics_prefix);
5230     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5231 }
5232
5233 void
5234 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5235 {
5236     if (rf == DROP_RANK) {
5237       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5238       sprintf(move, "%c@%c%c\n",
5239                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5240     } else {
5241         if (promoChar == 'x' || promoChar == NULLCHAR) {
5242           sprintf(move, "%c%c%c%c\n",
5243                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5244         } else {
5245             sprintf(move, "%c%c%c%c%c\n",
5246                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5247         }
5248     }
5249 }
5250
5251 void
5252 ProcessICSInitScript (FILE *f)
5253 {
5254     char buf[MSG_SIZ];
5255
5256     while (fgets(buf, MSG_SIZ, f)) {
5257         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5258     }
5259
5260     fclose(f);
5261 }
5262
5263
5264 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5265 static ClickType lastClickType;
5266
5267 void
5268 Sweep (int step)
5269 {
5270     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5271     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5272     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5273     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5274     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5275     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5276     do {
5277         promoSweep -= step;
5278         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5279         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5280         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5281         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5282         if(!step) step = -1;
5283     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5284             appData.testLegality && (promoSweep == king ||
5285             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5286     if(toX >= 0) {
5287         int victim = boards[currentMove][toY][toX];
5288         boards[currentMove][toY][toX] = promoSweep;
5289         DrawPosition(FALSE, boards[currentMove]);
5290         boards[currentMove][toY][toX] = victim;
5291     } else
5292     ChangeDragPiece(promoSweep);
5293 }
5294
5295 int
5296 PromoScroll (int x, int y)
5297 {
5298   int step = 0;
5299
5300   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5301   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5302   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5303   if(!step) return FALSE;
5304   lastX = x; lastY = y;
5305   if((promoSweep < BlackPawn) == flipView) step = -step;
5306   if(step > 0) selectFlag = 1;
5307   if(!selectFlag) Sweep(step);
5308   return FALSE;
5309 }
5310
5311 void
5312 NextPiece (int step)
5313 {
5314     ChessSquare piece = boards[currentMove][toY][toX];
5315     do {
5316         pieceSweep -= step;
5317         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5318         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5319         if(!step) step = -1;
5320     } while(PieceToChar(pieceSweep) == '.');
5321     boards[currentMove][toY][toX] = pieceSweep;
5322     DrawPosition(FALSE, boards[currentMove]);
5323     boards[currentMove][toY][toX] = piece;
5324 }
5325 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5326 void
5327 AlphaRank (char *move, int n)
5328 {
5329 //    char *p = move, c; int x, y;
5330
5331     if (appData.debugMode) {
5332         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5333     }
5334
5335     if(move[1]=='*' &&
5336        move[2]>='0' && move[2]<='9' &&
5337        move[3]>='a' && move[3]<='x'    ) {
5338         move[1] = '@';
5339         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5340         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5341     } else
5342     if(move[0]>='0' && move[0]<='9' &&
5343        move[1]>='a' && move[1]<='x' &&
5344        move[2]>='0' && move[2]<='9' &&
5345        move[3]>='a' && move[3]<='x'    ) {
5346         /* input move, Shogi -> normal */
5347         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5348         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5349         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5350         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5351     } else
5352     if(move[1]=='@' &&
5353        move[3]>='0' && move[3]<='9' &&
5354        move[2]>='a' && move[2]<='x'    ) {
5355         move[1] = '*';
5356         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5357         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5358     } else
5359     if(
5360        move[0]>='a' && move[0]<='x' &&
5361        move[3]>='0' && move[3]<='9' &&
5362        move[2]>='a' && move[2]<='x'    ) {
5363          /* output move, normal -> Shogi */
5364         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5365         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5366         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5367         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5368         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5369     }
5370     if (appData.debugMode) {
5371         fprintf(debugFP, "   out = '%s'\n", move);
5372     }
5373 }
5374
5375 char yy_textstr[8000];
5376
5377 /* Parser for moves from gnuchess, ICS, or user typein box */
5378 Boolean
5379 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5380 {
5381     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5382
5383     switch (*moveType) {
5384       case WhitePromotion:
5385       case BlackPromotion:
5386       case WhiteNonPromotion:
5387       case BlackNonPromotion:
5388       case NormalMove:
5389       case WhiteCapturesEnPassant:
5390       case BlackCapturesEnPassant:
5391       case WhiteKingSideCastle:
5392       case WhiteQueenSideCastle:
5393       case BlackKingSideCastle:
5394       case BlackQueenSideCastle:
5395       case WhiteKingSideCastleWild:
5396       case WhiteQueenSideCastleWild:
5397       case BlackKingSideCastleWild:
5398       case BlackQueenSideCastleWild:
5399       /* Code added by Tord: */
5400       case WhiteHSideCastleFR:
5401       case WhiteASideCastleFR:
5402       case BlackHSideCastleFR:
5403       case BlackASideCastleFR:
5404       /* End of code added by Tord */
5405       case IllegalMove:         /* bug or odd chess variant */
5406         *fromX = currentMoveString[0] - AAA;
5407         *fromY = currentMoveString[1] - ONE;
5408         *toX = currentMoveString[2] - AAA;
5409         *toY = currentMoveString[3] - ONE;
5410         *promoChar = currentMoveString[4];
5411         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5412             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5413     if (appData.debugMode) {
5414         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5415     }
5416             *fromX = *fromY = *toX = *toY = 0;
5417             return FALSE;
5418         }
5419         if (appData.testLegality) {
5420           return (*moveType != IllegalMove);
5421         } else {
5422           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5423                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5424         }
5425
5426       case WhiteDrop:
5427       case BlackDrop:
5428         *fromX = *moveType == WhiteDrop ?
5429           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5430           (int) CharToPiece(ToLower(currentMoveString[0]));
5431         *fromY = DROP_RANK;
5432         *toX = currentMoveString[2] - AAA;
5433         *toY = currentMoveString[3] - ONE;
5434         *promoChar = NULLCHAR;
5435         return TRUE;
5436
5437       case AmbiguousMove:
5438       case ImpossibleMove:
5439       case EndOfFile:
5440       case ElapsedTime:
5441       case Comment:
5442       case PGNTag:
5443       case NAG:
5444       case WhiteWins:
5445       case BlackWins:
5446       case GameIsDrawn:
5447       default:
5448     if (appData.debugMode) {
5449         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5450     }
5451         /* bug? */
5452         *fromX = *fromY = *toX = *toY = 0;
5453         *promoChar = NULLCHAR;
5454         return FALSE;
5455     }
5456 }
5457
5458 Boolean pushed = FALSE;
5459 char *lastParseAttempt;
5460
5461 void
5462 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5463 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5464   int fromX, fromY, toX, toY; char promoChar;
5465   ChessMove moveType;
5466   Boolean valid;
5467   int nr = 0;
5468
5469   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5470   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5471     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5472     pushed = TRUE;
5473   }
5474   endPV = forwardMostMove;
5475   do {
5476     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5477     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5478     lastParseAttempt = pv;
5479     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5480     if(!valid && nr == 0 &&
5481        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5482         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5483         // Hande case where played move is different from leading PV move
5484         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5485         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5486         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5487         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5488           endPV += 2; // if position different, keep this
5489           moveList[endPV-1][0] = fromX + AAA;
5490           moveList[endPV-1][1] = fromY + ONE;
5491           moveList[endPV-1][2] = toX + AAA;
5492           moveList[endPV-1][3] = toY + ONE;
5493           parseList[endPV-1][0] = NULLCHAR;
5494           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5495         }
5496       }
5497     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5498     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5499     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5500     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5501         valid++; // allow comments in PV
5502         continue;
5503     }
5504     nr++;
5505     if(endPV+1 > framePtr) break; // no space, truncate
5506     if(!valid) break;
5507     endPV++;
5508     CopyBoard(boards[endPV], boards[endPV-1]);
5509     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5510     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5511     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5512     CoordsToAlgebraic(boards[endPV - 1],
5513                              PosFlags(endPV - 1),
5514                              fromY, fromX, toY, toX, promoChar,
5515                              parseList[endPV - 1]);
5516   } while(valid);
5517   if(atEnd == 2) return; // used hidden, for PV conversion
5518   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5519   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5520   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5521                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5522   DrawPosition(TRUE, boards[currentMove]);
5523 }
5524
5525 int
5526 MultiPV (ChessProgramState *cps)
5527 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5528         int i;
5529         for(i=0; i<cps->nrOptions; i++)
5530             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5531                 return i;
5532         return -1;
5533 }
5534
5535 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5536
5537 Boolean
5538 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5539 {
5540         int startPV, multi, lineStart, origIndex = index;
5541         char *p, buf2[MSG_SIZ];
5542         ChessProgramState *cps = (pane ? &second : &first);
5543
5544         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5545         lastX = x; lastY = y;
5546         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5547         lineStart = startPV = index;
5548         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5549         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5550         index = startPV;
5551         do{ while(buf[index] && buf[index] != '\n') index++;
5552         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5553         buf[index] = 0;
5554         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5555                 int n = cps->option[multi].value;
5556                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5557                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5558                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5559                 cps->option[multi].value = n;
5560                 *start = *end = 0;
5561                 return FALSE;
5562         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5563                 ExcludeClick(origIndex - lineStart);
5564                 return FALSE;
5565         }
5566         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5567         *start = startPV; *end = index-1;
5568         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5569         return TRUE;
5570 }
5571
5572 char *
5573 PvToSAN (char *pv)
5574 {
5575         static char buf[10*MSG_SIZ];
5576         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5577         *buf = NULLCHAR;
5578         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5579         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5580         for(i = forwardMostMove; i<endPV; i++){
5581             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5582             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5583             k += strlen(buf+k);
5584         }
5585         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5586         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5587         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5588         endPV = savedEnd;
5589         return buf;
5590 }
5591
5592 Boolean
5593 LoadPV (int x, int y)
5594 { // called on right mouse click to load PV
5595   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5596   lastX = x; lastY = y;
5597   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5598   extendGame = FALSE;
5599   return TRUE;
5600 }
5601
5602 void
5603 UnLoadPV ()
5604 {
5605   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5606   if(endPV < 0) return;
5607   if(appData.autoCopyPV) CopyFENToClipboard();
5608   endPV = -1;
5609   if(extendGame && currentMove > forwardMostMove) {
5610         Boolean saveAnimate = appData.animate;
5611         if(pushed) {
5612             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5613                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5614             } else storedGames--; // abandon shelved tail of original game
5615         }
5616         pushed = FALSE;
5617         forwardMostMove = currentMove;
5618         currentMove = oldFMM;
5619         appData.animate = FALSE;
5620         ToNrEvent(forwardMostMove);
5621         appData.animate = saveAnimate;
5622   }
5623   currentMove = forwardMostMove;
5624   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5625   ClearPremoveHighlights();
5626   DrawPosition(TRUE, boards[currentMove]);
5627 }
5628
5629 void
5630 MovePV (int x, int y, int h)
5631 { // step through PV based on mouse coordinates (called on mouse move)
5632   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5633
5634   // we must somehow check if right button is still down (might be released off board!)
5635   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5636   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5637   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5638   if(!step) return;
5639   lastX = x; lastY = y;
5640
5641   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5642   if(endPV < 0) return;
5643   if(y < margin) step = 1; else
5644   if(y > h - margin) step = -1;
5645   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5646   currentMove += step;
5647   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5648   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5649                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5650   DrawPosition(FALSE, boards[currentMove]);
5651 }
5652
5653
5654 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5655 // All positions will have equal probability, but the current method will not provide a unique
5656 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5657 #define DARK 1
5658 #define LITE 2
5659 #define ANY 3
5660
5661 int squaresLeft[4];
5662 int piecesLeft[(int)BlackPawn];
5663 int seed, nrOfShuffles;
5664
5665 void
5666 GetPositionNumber ()
5667 {       // sets global variable seed
5668         int i;
5669
5670         seed = appData.defaultFrcPosition;
5671         if(seed < 0) { // randomize based on time for negative FRC position numbers
5672                 for(i=0; i<50; i++) seed += random();
5673                 seed = random() ^ random() >> 8 ^ random() << 8;
5674                 if(seed<0) seed = -seed;
5675         }
5676 }
5677
5678 int
5679 put (Board board, int pieceType, int rank, int n, int shade)
5680 // put the piece on the (n-1)-th empty squares of the given shade
5681 {
5682         int i;
5683
5684         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5685                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5686                         board[rank][i] = (ChessSquare) pieceType;
5687                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5688                         squaresLeft[ANY]--;
5689                         piecesLeft[pieceType]--;
5690                         return i;
5691                 }
5692         }
5693         return -1;
5694 }
5695
5696
5697 void
5698 AddOnePiece (Board board, int pieceType, int rank, int shade)
5699 // calculate where the next piece goes, (any empty square), and put it there
5700 {
5701         int i;
5702
5703         i = seed % squaresLeft[shade];
5704         nrOfShuffles *= squaresLeft[shade];
5705         seed /= squaresLeft[shade];
5706         put(board, pieceType, rank, i, shade);
5707 }
5708
5709 void
5710 AddTwoPieces (Board board, int pieceType, int rank)
5711 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5712 {
5713         int i, n=squaresLeft[ANY], j=n-1, k;
5714
5715         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5716         i = seed % k;  // pick one
5717         nrOfShuffles *= k;
5718         seed /= k;
5719         while(i >= j) i -= j--;
5720         j = n - 1 - j; i += j;
5721         put(board, pieceType, rank, j, ANY);
5722         put(board, pieceType, rank, i, ANY);
5723 }
5724
5725 void
5726 SetUpShuffle (Board board, int number)
5727 {
5728         int i, p, first=1;
5729
5730         GetPositionNumber(); nrOfShuffles = 1;
5731
5732         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5733         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5734         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5735
5736         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5737
5738         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5739             p = (int) board[0][i];
5740             if(p < (int) BlackPawn) piecesLeft[p] ++;
5741             board[0][i] = EmptySquare;
5742         }
5743
5744         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5745             // shuffles restricted to allow normal castling put KRR first
5746             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5747                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5748             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5749                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5750             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5751                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5752             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5753                 put(board, WhiteRook, 0, 0, ANY);
5754             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5755         }
5756
5757         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5758             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5759             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5760                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5761                 while(piecesLeft[p] >= 2) {
5762                     AddOnePiece(board, p, 0, LITE);
5763                     AddOnePiece(board, p, 0, DARK);
5764                 }
5765                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5766             }
5767
5768         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5769             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5770             // but we leave King and Rooks for last, to possibly obey FRC restriction
5771             if(p == (int)WhiteRook) continue;
5772             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5773             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5774         }
5775
5776         // now everything is placed, except perhaps King (Unicorn) and Rooks
5777
5778         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5779             // Last King gets castling rights
5780             while(piecesLeft[(int)WhiteUnicorn]) {
5781                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5782                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5783             }
5784
5785             while(piecesLeft[(int)WhiteKing]) {
5786                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5787                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5788             }
5789
5790
5791         } else {
5792             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5793             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5794         }
5795
5796         // Only Rooks can be left; simply place them all
5797         while(piecesLeft[(int)WhiteRook]) {
5798                 i = put(board, WhiteRook, 0, 0, ANY);
5799                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5800                         if(first) {
5801                                 first=0;
5802                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5803                         }
5804                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5805                 }
5806         }
5807         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5808             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5809         }
5810
5811         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5812 }
5813
5814 int
5815 SetCharTable (char *table, const char * map)
5816 /* [HGM] moved here from winboard.c because of its general usefulness */
5817 /*       Basically a safe strcpy that uses the last character as King */
5818 {
5819     int result = FALSE; int NrPieces;
5820
5821     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5822                     && NrPieces >= 12 && !(NrPieces&1)) {
5823         int i; /* [HGM] Accept even length from 12 to 34 */
5824
5825         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5826         for( i=0; i<NrPieces/2-1; i++ ) {
5827             table[i] = map[i];
5828             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5829         }
5830         table[(int) WhiteKing]  = map[NrPieces/2-1];
5831         table[(int) BlackKing]  = map[NrPieces-1];
5832
5833         result = TRUE;
5834     }
5835
5836     return result;
5837 }
5838
5839 void
5840 Prelude (Board board)
5841 {       // [HGM] superchess: random selection of exo-pieces
5842         int i, j, k; ChessSquare p;
5843         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5844
5845         GetPositionNumber(); // use FRC position number
5846
5847         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5848             SetCharTable(pieceToChar, appData.pieceToCharTable);
5849             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5850                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5851         }
5852
5853         j = seed%4;                 seed /= 4;
5854         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5855         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5856         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5857         j = seed%3 + (seed%3 >= j); seed /= 3;
5858         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5859         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5860         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5861         j = seed%3;                 seed /= 3;
5862         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5863         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5864         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5865         j = seed%2 + (seed%2 >= j); seed /= 2;
5866         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5867         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5868         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5869         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5870         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5871         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5872         put(board, exoPieces[0],    0, 0, ANY);
5873         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5874 }
5875
5876 void
5877 InitPosition (int redraw)
5878 {
5879     ChessSquare (* pieces)[BOARD_FILES];
5880     int i, j, pawnRow=1, pieceRows=1, overrule,
5881     oldx = gameInfo.boardWidth,
5882     oldy = gameInfo.boardHeight,
5883     oldh = gameInfo.holdingsWidth;
5884     static int oldv;
5885
5886     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5887
5888     /* [AS] Initialize pv info list [HGM] and game status */
5889     {
5890         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5891             pvInfoList[i].depth = 0;
5892             boards[i][EP_STATUS] = EP_NONE;
5893             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5894         }
5895
5896         initialRulePlies = 0; /* 50-move counter start */
5897
5898         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5899         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5900     }
5901
5902
5903     /* [HGM] logic here is completely changed. In stead of full positions */
5904     /* the initialized data only consist of the two backranks. The switch */
5905     /* selects which one we will use, which is than copied to the Board   */
5906     /* initialPosition, which for the rest is initialized by Pawns and    */
5907     /* empty squares. This initial position is then copied to boards[0],  */
5908     /* possibly after shuffling, so that it remains available.            */
5909
5910     gameInfo.holdingsWidth = 0; /* default board sizes */
5911     gameInfo.boardWidth    = 8;
5912     gameInfo.boardHeight   = 8;
5913     gameInfo.holdingsSize  = 0;
5914     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5915     for(i=0; i<BOARD_FILES-2; i++)
5916       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5917     initialPosition[EP_STATUS] = EP_NONE;
5918     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5919     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5920          SetCharTable(pieceNickName, appData.pieceNickNames);
5921     else SetCharTable(pieceNickName, "............");
5922     pieces = FIDEArray;
5923
5924     switch (gameInfo.variant) {
5925     case VariantFischeRandom:
5926       shuffleOpenings = TRUE;
5927     default:
5928       break;
5929     case VariantShatranj:
5930       pieces = ShatranjArray;
5931       nrCastlingRights = 0;
5932       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5933       break;
5934     case VariantMakruk:
5935       pieces = makrukArray;
5936       nrCastlingRights = 0;
5937       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5938       break;
5939     case VariantASEAN:
5940       pieces = aseanArray;
5941       nrCastlingRights = 0;
5942       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5943       break;
5944     case VariantTwoKings:
5945       pieces = twoKingsArray;
5946       break;
5947     case VariantGrand:
5948       pieces = GrandArray;
5949       nrCastlingRights = 0;
5950       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5951       gameInfo.boardWidth = 10;
5952       gameInfo.boardHeight = 10;
5953       gameInfo.holdingsSize = 7;
5954       break;
5955     case VariantCapaRandom:
5956       shuffleOpenings = TRUE;
5957     case VariantCapablanca:
5958       pieces = CapablancaArray;
5959       gameInfo.boardWidth = 10;
5960       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5961       break;
5962     case VariantGothic:
5963       pieces = GothicArray;
5964       gameInfo.boardWidth = 10;
5965       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5966       break;
5967     case VariantSChess:
5968       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5969       gameInfo.holdingsSize = 7;
5970       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5971       break;
5972     case VariantJanus:
5973       pieces = JanusArray;
5974       gameInfo.boardWidth = 10;
5975       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5976       nrCastlingRights = 6;
5977         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5978         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5979         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5980         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5981         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5982         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5983       break;
5984     case VariantFalcon:
5985       pieces = FalconArray;
5986       gameInfo.boardWidth = 10;
5987       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5988       break;
5989     case VariantXiangqi:
5990       pieces = XiangqiArray;
5991       gameInfo.boardWidth  = 9;
5992       gameInfo.boardHeight = 10;
5993       nrCastlingRights = 0;
5994       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5995       break;
5996     case VariantShogi:
5997       pieces = ShogiArray;
5998       gameInfo.boardWidth  = 9;
5999       gameInfo.boardHeight = 9;
6000       gameInfo.holdingsSize = 7;
6001       nrCastlingRights = 0;
6002       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6003       break;
6004     case VariantChu:
6005       pieces = ChuArray; pieceRows = 3;
6006       gameInfo.boardWidth  = 12;
6007       gameInfo.boardHeight = 12;
6008       nrCastlingRights = 0;
6009       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.+++++++++++++.+++++K"
6010                                 "p.brqsexogcathd.vmlifn+.+++++++++++++.+++++k");
6011       break;
6012     case VariantCourier:
6013       pieces = CourierArray;
6014       gameInfo.boardWidth  = 12;
6015       nrCastlingRights = 0;
6016       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6017       break;
6018     case VariantKnightmate:
6019       pieces = KnightmateArray;
6020       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6021       break;
6022     case VariantSpartan:
6023       pieces = SpartanArray;
6024       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6025       break;
6026     case VariantFairy:
6027       pieces = fairyArray;
6028       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6029       break;
6030     case VariantGreat:
6031       pieces = GreatArray;
6032       gameInfo.boardWidth = 10;
6033       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6034       gameInfo.holdingsSize = 8;
6035       break;
6036     case VariantSuper:
6037       pieces = FIDEArray;
6038       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6039       gameInfo.holdingsSize = 8;
6040       startedFromSetupPosition = TRUE;
6041       break;
6042     case VariantCrazyhouse:
6043     case VariantBughouse:
6044       pieces = FIDEArray;
6045       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6046       gameInfo.holdingsSize = 5;
6047       break;
6048     case VariantWildCastle:
6049       pieces = FIDEArray;
6050       /* !!?shuffle with kings guaranteed to be on d or e file */
6051       shuffleOpenings = 1;
6052       break;
6053     case VariantNoCastle:
6054       pieces = FIDEArray;
6055       nrCastlingRights = 0;
6056       /* !!?unconstrained back-rank shuffle */
6057       shuffleOpenings = 1;
6058       break;
6059     }
6060
6061     overrule = 0;
6062     if(appData.NrFiles >= 0) {
6063         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6064         gameInfo.boardWidth = appData.NrFiles;
6065     }
6066     if(appData.NrRanks >= 0) {
6067         gameInfo.boardHeight = appData.NrRanks;
6068     }
6069     if(appData.holdingsSize >= 0) {
6070         i = appData.holdingsSize;
6071         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6072         gameInfo.holdingsSize = i;
6073     }
6074     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6075     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6076         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6077
6078     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6079     if(pawnRow < 1) pawnRow = 1;
6080     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6081     if(gameInfo.variant == VariantChu) pawnRow = 3;
6082
6083     /* User pieceToChar list overrules defaults */
6084     if(appData.pieceToCharTable != NULL)
6085         SetCharTable(pieceToChar, appData.pieceToCharTable);
6086
6087     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6088
6089         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6090             s = (ChessSquare) 0; /* account holding counts in guard band */
6091         for( i=0; i<BOARD_HEIGHT; i++ )
6092             initialPosition[i][j] = s;
6093
6094         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6095         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6096         initialPosition[pawnRow][j] = WhitePawn;
6097         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6098         if(gameInfo.variant == VariantXiangqi) {
6099             if(j&1) {
6100                 initialPosition[pawnRow][j] =
6101                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6102                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6103                    initialPosition[2][j] = WhiteCannon;
6104                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6105                 }
6106             }
6107         }
6108         if(gameInfo.variant == VariantChu) {
6109              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6110                initialPosition[pawnRow+1][j] = WhiteCobra,
6111                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6112              for(i=1; i<pieceRows; i++) {
6113                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6114                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6115              }
6116         }
6117         if(gameInfo.variant == VariantGrand) {
6118             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6119                initialPosition[0][j] = WhiteRook;
6120                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6121             }
6122         }
6123         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6124     }
6125     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6126
6127             j=BOARD_LEFT+1;
6128             initialPosition[1][j] = WhiteBishop;
6129             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6130             j=BOARD_RGHT-2;
6131             initialPosition[1][j] = WhiteRook;
6132             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6133     }
6134
6135     if( nrCastlingRights == -1) {
6136         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6137         /*       This sets default castling rights from none to normal corners   */
6138         /* Variants with other castling rights must set them themselves above    */
6139         nrCastlingRights = 6;
6140
6141         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6142         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6143         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6144         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6145         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6146         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6147      }
6148
6149      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6150      if(gameInfo.variant == VariantGreat) { // promotion commoners
6151         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6152         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6153         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6154         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6155      }
6156      if( gameInfo.variant == VariantSChess ) {
6157       initialPosition[1][0] = BlackMarshall;
6158       initialPosition[2][0] = BlackAngel;
6159       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6160       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6161       initialPosition[1][1] = initialPosition[2][1] =
6162       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6163      }
6164   if (appData.debugMode) {
6165     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6166   }
6167     if(shuffleOpenings) {
6168         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6169         startedFromSetupPosition = TRUE;
6170     }
6171     if(startedFromPositionFile) {
6172       /* [HGM] loadPos: use PositionFile for every new game */
6173       CopyBoard(initialPosition, filePosition);
6174       for(i=0; i<nrCastlingRights; i++)
6175           initialRights[i] = filePosition[CASTLING][i];
6176       startedFromSetupPosition = TRUE;
6177     }
6178
6179     CopyBoard(boards[0], initialPosition);
6180
6181     if(oldx != gameInfo.boardWidth ||
6182        oldy != gameInfo.boardHeight ||
6183        oldv != gameInfo.variant ||
6184        oldh != gameInfo.holdingsWidth
6185                                          )
6186             InitDrawingSizes(-2 ,0);
6187
6188     oldv = gameInfo.variant;
6189     if (redraw)
6190       DrawPosition(TRUE, boards[currentMove]);
6191 }
6192
6193 void
6194 SendBoard (ChessProgramState *cps, int moveNum)
6195 {
6196     char message[MSG_SIZ];
6197
6198     if (cps->useSetboard) {
6199       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6200       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6201       SendToProgram(message, cps);
6202       free(fen);
6203
6204     } else {
6205       ChessSquare *bp;
6206       int i, j, left=0, right=BOARD_WIDTH;
6207       /* Kludge to set black to move, avoiding the troublesome and now
6208        * deprecated "black" command.
6209        */
6210       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6211         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6212
6213       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6214
6215       SendToProgram("edit\n", cps);
6216       SendToProgram("#\n", cps);
6217       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6218         bp = &boards[moveNum][i][left];
6219         for (j = left; j < right; j++, bp++) {
6220           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6221           if ((int) *bp < (int) BlackPawn) {
6222             if(j == BOARD_RGHT+1)
6223                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6224             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6225             if(message[0] == '+' || message[0] == '~') {
6226               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6227                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6228                         AAA + j, ONE + i);
6229             }
6230             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6231                 message[1] = BOARD_RGHT   - 1 - j + '1';
6232                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6233             }
6234             SendToProgram(message, cps);
6235           }
6236         }
6237       }
6238
6239       SendToProgram("c\n", cps);
6240       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6241         bp = &boards[moveNum][i][left];
6242         for (j = left; j < right; j++, bp++) {
6243           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6244           if (((int) *bp != (int) EmptySquare)
6245               && ((int) *bp >= (int) BlackPawn)) {
6246             if(j == BOARD_LEFT-2)
6247                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6248             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6249                     AAA + j, ONE + i);
6250             if(message[0] == '+' || message[0] == '~') {
6251               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6252                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6253                         AAA + j, ONE + i);
6254             }
6255             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6256                 message[1] = BOARD_RGHT   - 1 - j + '1';
6257                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6258             }
6259             SendToProgram(message, cps);
6260           }
6261         }
6262       }
6263
6264       SendToProgram(".\n", cps);
6265     }
6266     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6267 }
6268
6269 char exclusionHeader[MSG_SIZ];
6270 int exCnt, excludePtr;
6271 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6272 static Exclusion excluTab[200];
6273 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6274
6275 static void
6276 WriteMap (int s)
6277 {
6278     int j;
6279     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6280     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6281 }
6282
6283 static void
6284 ClearMap ()
6285 {
6286     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6287     excludePtr = 24; exCnt = 0;
6288     WriteMap(0);
6289 }
6290
6291 static void
6292 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6293 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6294     char buf[2*MOVE_LEN], *p;
6295     Exclusion *e = excluTab;
6296     int i;
6297     for(i=0; i<exCnt; i++)
6298         if(e[i].ff == fromX && e[i].fr == fromY &&
6299            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6300     if(i == exCnt) { // was not in exclude list; add it
6301         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6302         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6303             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6304             return; // abort
6305         }
6306         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6307         excludePtr++; e[i].mark = excludePtr++;
6308         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6309         exCnt++;
6310     }
6311     exclusionHeader[e[i].mark] = state;
6312 }
6313
6314 static int
6315 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6316 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6317     char buf[MSG_SIZ];
6318     int j, k;
6319     ChessMove moveType;
6320     if((signed char)promoChar == -1) { // kludge to indicate best move
6321         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6322             return 1; // if unparsable, abort
6323     }
6324     // update exclusion map (resolving toggle by consulting existing state)
6325     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6326     j = k%8; k >>= 3;
6327     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6328     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6329          excludeMap[k] |=   1<<j;
6330     else excludeMap[k] &= ~(1<<j);
6331     // update header
6332     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6333     // inform engine
6334     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6335     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6336     SendToBoth(buf);
6337     return (state == '+');
6338 }
6339
6340 static void
6341 ExcludeClick (int index)
6342 {
6343     int i, j;
6344     Exclusion *e = excluTab;
6345     if(index < 25) { // none, best or tail clicked
6346         if(index < 13) { // none: include all
6347             WriteMap(0); // clear map
6348             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6349             SendToBoth("include all\n"); // and inform engine
6350         } else if(index > 18) { // tail
6351             if(exclusionHeader[19] == '-') { // tail was excluded
6352                 SendToBoth("include all\n");
6353                 WriteMap(0); // clear map completely
6354                 // now re-exclude selected moves
6355                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6356                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6357             } else { // tail was included or in mixed state
6358                 SendToBoth("exclude all\n");
6359                 WriteMap(0xFF); // fill map completely
6360                 // now re-include selected moves
6361                 j = 0; // count them
6362                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6363                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6364                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6365             }
6366         } else { // best
6367             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6368         }
6369     } else {
6370         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6371             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6372             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6373             break;
6374         }
6375     }
6376 }
6377
6378 ChessSquare
6379 DefaultPromoChoice (int white)
6380 {
6381     ChessSquare result;
6382     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6383        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6384         result = WhiteFerz; // no choice
6385     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6386         result= WhiteKing; // in Suicide Q is the last thing we want
6387     else if(gameInfo.variant == VariantSpartan)
6388         result = white ? WhiteQueen : WhiteAngel;
6389     else result = WhiteQueen;
6390     if(!white) result = WHITE_TO_BLACK result;
6391     return result;
6392 }
6393
6394 static int autoQueen; // [HGM] oneclick
6395
6396 int
6397 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6398 {
6399     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6400     /* [HGM] add Shogi promotions */
6401     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6402     ChessSquare piece;
6403     ChessMove moveType;
6404     Boolean premove;
6405
6406     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6407     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6408
6409     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6410       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6411         return FALSE;
6412
6413     piece = boards[currentMove][fromY][fromX];
6414     if(gameInfo.variant == VariantShogi) {
6415         promotionZoneSize = BOARD_HEIGHT/3;
6416         highestPromotingPiece = (int)WhiteFerz;
6417     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6418         promotionZoneSize = 3;
6419     }
6420
6421     // Treat Lance as Pawn when it is not representing Amazon
6422     if(gameInfo.variant != VariantSuper) {
6423         if(piece == WhiteLance) piece = WhitePawn; else
6424         if(piece == BlackLance) piece = BlackPawn;
6425     }
6426
6427     // next weed out all moves that do not touch the promotion zone at all
6428     if((int)piece >= BlackPawn) {
6429         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6430              return FALSE;
6431         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6432     } else {
6433         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6434            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6435     }
6436
6437     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6438
6439     // weed out mandatory Shogi promotions
6440     if(gameInfo.variant == VariantShogi) {
6441         if(piece >= BlackPawn) {
6442             if(toY == 0 && piece == BlackPawn ||
6443                toY == 0 && piece == BlackQueen ||
6444                toY <= 1 && piece == BlackKnight) {
6445                 *promoChoice = '+';
6446                 return FALSE;
6447             }
6448         } else {
6449             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6450                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6451                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6452                 *promoChoice = '+';
6453                 return FALSE;
6454             }
6455         }
6456     }
6457
6458     // weed out obviously illegal Pawn moves
6459     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6460         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6461         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6462         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6463         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6464         // note we are not allowed to test for valid (non-)capture, due to premove
6465     }
6466
6467     // we either have a choice what to promote to, or (in Shogi) whether to promote
6468     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6469        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6470         *promoChoice = PieceToChar(BlackFerz);  // no choice
6471         return FALSE;
6472     }
6473     // no sense asking what we must promote to if it is going to explode...
6474     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6475         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6476         return FALSE;
6477     }
6478     // give caller the default choice even if we will not make it
6479     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6480     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6481     if(        sweepSelect && gameInfo.variant != VariantGreat
6482                            && gameInfo.variant != VariantGrand
6483                            && gameInfo.variant != VariantSuper) return FALSE;
6484     if(autoQueen) return FALSE; // predetermined
6485
6486     // suppress promotion popup on illegal moves that are not premoves
6487     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6488               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6489     if(appData.testLegality && !premove) {
6490         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6491                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6492         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6493             return FALSE;
6494     }
6495
6496     return TRUE;
6497 }
6498
6499 int
6500 InPalace (int row, int column)
6501 {   /* [HGM] for Xiangqi */
6502     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6503          column < (BOARD_WIDTH + 4)/2 &&
6504          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6505     return FALSE;
6506 }
6507
6508 int
6509 PieceForSquare (int x, int y)
6510 {
6511   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6512      return -1;
6513   else
6514      return boards[currentMove][y][x];
6515 }
6516
6517 int
6518 OKToStartUserMove (int x, int y)
6519 {
6520     ChessSquare from_piece;
6521     int white_piece;
6522
6523     if (matchMode) return FALSE;
6524     if (gameMode == EditPosition) return TRUE;
6525
6526     if (x >= 0 && y >= 0)
6527       from_piece = boards[currentMove][y][x];
6528     else
6529       from_piece = EmptySquare;
6530
6531     if (from_piece == EmptySquare) return FALSE;
6532
6533     white_piece = (int)from_piece >= (int)WhitePawn &&
6534       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6535
6536     switch (gameMode) {
6537       case AnalyzeFile:
6538       case TwoMachinesPlay:
6539       case EndOfGame:
6540         return FALSE;
6541
6542       case IcsObserving:
6543       case IcsIdle:
6544         return FALSE;
6545
6546       case MachinePlaysWhite:
6547       case IcsPlayingBlack:
6548         if (appData.zippyPlay) return FALSE;
6549         if (white_piece) {
6550             DisplayMoveError(_("You are playing Black"));
6551             return FALSE;
6552         }
6553         break;
6554
6555       case MachinePlaysBlack:
6556       case IcsPlayingWhite:
6557         if (appData.zippyPlay) return FALSE;
6558         if (!white_piece) {
6559             DisplayMoveError(_("You are playing White"));
6560             return FALSE;
6561         }
6562         break;
6563
6564       case PlayFromGameFile:
6565             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6566       case EditGame:
6567         if (!white_piece && WhiteOnMove(currentMove)) {
6568             DisplayMoveError(_("It is White's turn"));
6569             return FALSE;
6570         }
6571         if (white_piece && !WhiteOnMove(currentMove)) {
6572             DisplayMoveError(_("It is Black's turn"));
6573             return FALSE;
6574         }
6575         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6576             /* Editing correspondence game history */
6577             /* Could disallow this or prompt for confirmation */
6578             cmailOldMove = -1;
6579         }
6580         break;
6581
6582       case BeginningOfGame:
6583         if (appData.icsActive) return FALSE;
6584         if (!appData.noChessProgram) {
6585             if (!white_piece) {
6586                 DisplayMoveError(_("You are playing White"));
6587                 return FALSE;
6588             }
6589         }
6590         break;
6591
6592       case Training:
6593         if (!white_piece && WhiteOnMove(currentMove)) {
6594             DisplayMoveError(_("It is White's turn"));
6595             return FALSE;
6596         }
6597         if (white_piece && !WhiteOnMove(currentMove)) {
6598             DisplayMoveError(_("It is Black's turn"));
6599             return FALSE;
6600         }
6601         break;
6602
6603       default:
6604       case IcsExamining:
6605         break;
6606     }
6607     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6608         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6609         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6610         && gameMode != AnalyzeFile && gameMode != Training) {
6611         DisplayMoveError(_("Displayed position is not current"));
6612         return FALSE;
6613     }
6614     return TRUE;
6615 }
6616
6617 Boolean
6618 OnlyMove (int *x, int *y, Boolean captures)
6619 {
6620     DisambiguateClosure cl;
6621     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6622     switch(gameMode) {
6623       case MachinePlaysBlack:
6624       case IcsPlayingWhite:
6625       case BeginningOfGame:
6626         if(!WhiteOnMove(currentMove)) return FALSE;
6627         break;
6628       case MachinePlaysWhite:
6629       case IcsPlayingBlack:
6630         if(WhiteOnMove(currentMove)) return FALSE;
6631         break;
6632       case EditGame:
6633         break;
6634       default:
6635         return FALSE;
6636     }
6637     cl.pieceIn = EmptySquare;
6638     cl.rfIn = *y;
6639     cl.ffIn = *x;
6640     cl.rtIn = -1;
6641     cl.ftIn = -1;
6642     cl.promoCharIn = NULLCHAR;
6643     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6644     if( cl.kind == NormalMove ||
6645         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6646         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6647         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6648       fromX = cl.ff;
6649       fromY = cl.rf;
6650       *x = cl.ft;
6651       *y = cl.rt;
6652       return TRUE;
6653     }
6654     if(cl.kind != ImpossibleMove) return FALSE;
6655     cl.pieceIn = EmptySquare;
6656     cl.rfIn = -1;
6657     cl.ffIn = -1;
6658     cl.rtIn = *y;
6659     cl.ftIn = *x;
6660     cl.promoCharIn = NULLCHAR;
6661     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6662     if( cl.kind == NormalMove ||
6663         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6664         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6665         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6666       fromX = cl.ff;
6667       fromY = cl.rf;
6668       *x = cl.ft;
6669       *y = cl.rt;
6670       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6671       return TRUE;
6672     }
6673     return FALSE;
6674 }
6675
6676 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6677 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6678 int lastLoadGameUseList = FALSE;
6679 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6680 ChessMove lastLoadGameStart = EndOfFile;
6681 int doubleClick;
6682
6683 void
6684 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6685 {
6686     ChessMove moveType;
6687     ChessSquare pup;
6688     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6689
6690     /* Check if the user is playing in turn.  This is complicated because we
6691        let the user "pick up" a piece before it is his turn.  So the piece he
6692        tried to pick up may have been captured by the time he puts it down!
6693        Therefore we use the color the user is supposed to be playing in this
6694        test, not the color of the piece that is currently on the starting
6695        square---except in EditGame mode, where the user is playing both
6696        sides; fortunately there the capture race can't happen.  (It can
6697        now happen in IcsExamining mode, but that's just too bad.  The user
6698        will get a somewhat confusing message in that case.)
6699        */
6700
6701     switch (gameMode) {
6702       case AnalyzeFile:
6703       case TwoMachinesPlay:
6704       case EndOfGame:
6705       case IcsObserving:
6706       case IcsIdle:
6707         /* We switched into a game mode where moves are not accepted,
6708            perhaps while the mouse button was down. */
6709         return;
6710
6711       case MachinePlaysWhite:
6712         /* User is moving for Black */
6713         if (WhiteOnMove(currentMove)) {
6714             DisplayMoveError(_("It is White's turn"));
6715             return;
6716         }
6717         break;
6718
6719       case MachinePlaysBlack:
6720         /* User is moving for White */
6721         if (!WhiteOnMove(currentMove)) {
6722             DisplayMoveError(_("It is Black's turn"));
6723             return;
6724         }
6725         break;
6726
6727       case PlayFromGameFile:
6728             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6729       case EditGame:
6730       case IcsExamining:
6731       case BeginningOfGame:
6732       case AnalyzeMode:
6733       case Training:
6734         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6735         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6736             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6737             /* User is moving for Black */
6738             if (WhiteOnMove(currentMove)) {
6739                 DisplayMoveError(_("It is White's turn"));
6740                 return;
6741             }
6742         } else {
6743             /* User is moving for White */
6744             if (!WhiteOnMove(currentMove)) {
6745                 DisplayMoveError(_("It is Black's turn"));
6746                 return;
6747             }
6748         }
6749         break;
6750
6751       case IcsPlayingBlack:
6752         /* User is moving for Black */
6753         if (WhiteOnMove(currentMove)) {
6754             if (!appData.premove) {
6755                 DisplayMoveError(_("It is White's turn"));
6756             } else if (toX >= 0 && toY >= 0) {
6757                 premoveToX = toX;
6758                 premoveToY = toY;
6759                 premoveFromX = fromX;
6760                 premoveFromY = fromY;
6761                 premovePromoChar = promoChar;
6762                 gotPremove = 1;
6763                 if (appData.debugMode)
6764                     fprintf(debugFP, "Got premove: fromX %d,"
6765                             "fromY %d, toX %d, toY %d\n",
6766                             fromX, fromY, toX, toY);
6767             }
6768             return;
6769         }
6770         break;
6771
6772       case IcsPlayingWhite:
6773         /* User is moving for White */
6774         if (!WhiteOnMove(currentMove)) {
6775             if (!appData.premove) {
6776                 DisplayMoveError(_("It is Black's turn"));
6777             } else if (toX >= 0 && toY >= 0) {
6778                 premoveToX = toX;
6779                 premoveToY = toY;
6780                 premoveFromX = fromX;
6781                 premoveFromY = fromY;
6782                 premovePromoChar = promoChar;
6783                 gotPremove = 1;
6784                 if (appData.debugMode)
6785                     fprintf(debugFP, "Got premove: fromX %d,"
6786                             "fromY %d, toX %d, toY %d\n",
6787                             fromX, fromY, toX, toY);
6788             }
6789             return;
6790         }
6791         break;
6792
6793       default:
6794         break;
6795
6796       case EditPosition:
6797         /* EditPosition, empty square, or different color piece;
6798            click-click move is possible */
6799         if (toX == -2 || toY == -2) {
6800             boards[0][fromY][fromX] = EmptySquare;
6801             DrawPosition(FALSE, boards[currentMove]);
6802             return;
6803         } else if (toX >= 0 && toY >= 0) {
6804             boards[0][toY][toX] = boards[0][fromY][fromX];
6805             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6806                 if(boards[0][fromY][0] != EmptySquare) {
6807                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6808                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6809                 }
6810             } else
6811             if(fromX == BOARD_RGHT+1) {
6812                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6813                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6814                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6815                 }
6816             } else
6817             boards[0][fromY][fromX] = gatingPiece;
6818             DrawPosition(FALSE, boards[currentMove]);
6819             return;
6820         }
6821         return;
6822     }
6823
6824     if(toX < 0 || toY < 0) return;
6825     pup = boards[currentMove][toY][toX];
6826
6827     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6828     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6829          if( pup != EmptySquare ) return;
6830          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6831            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6832                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6833            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6834            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6835            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6836            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6837          fromY = DROP_RANK;
6838     }
6839
6840     /* [HGM] always test for legality, to get promotion info */
6841     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6842                                          fromY, fromX, toY, toX, promoChar);
6843
6844     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6845
6846     /* [HGM] but possibly ignore an IllegalMove result */
6847     if (appData.testLegality) {
6848         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6849             DisplayMoveError(_("Illegal move"));
6850             return;
6851         }
6852     }
6853
6854     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6855         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6856              ClearPremoveHighlights(); // was included
6857         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6858         return;
6859     }
6860
6861     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6862 }
6863
6864 /* Common tail of UserMoveEvent and DropMenuEvent */
6865 int
6866 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6867 {
6868     char *bookHit = 0;
6869
6870     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6871         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6872         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6873         if(WhiteOnMove(currentMove)) {
6874             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6875         } else {
6876             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6877         }
6878     }
6879
6880     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6881        move type in caller when we know the move is a legal promotion */
6882     if(moveType == NormalMove && promoChar)
6883         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6884
6885     /* [HGM] <popupFix> The following if has been moved here from
6886        UserMoveEvent(). Because it seemed to belong here (why not allow
6887        piece drops in training games?), and because it can only be
6888        performed after it is known to what we promote. */
6889     if (gameMode == Training) {
6890       /* compare the move played on the board to the next move in the
6891        * game. If they match, display the move and the opponent's response.
6892        * If they don't match, display an error message.
6893        */
6894       int saveAnimate;
6895       Board testBoard;
6896       CopyBoard(testBoard, boards[currentMove]);
6897       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6898
6899       if (CompareBoards(testBoard, boards[currentMove+1])) {
6900         ForwardInner(currentMove+1);
6901
6902         /* Autoplay the opponent's response.
6903          * if appData.animate was TRUE when Training mode was entered,
6904          * the response will be animated.
6905          */
6906         saveAnimate = appData.animate;
6907         appData.animate = animateTraining;
6908         ForwardInner(currentMove+1);
6909         appData.animate = saveAnimate;
6910
6911         /* check for the end of the game */
6912         if (currentMove >= forwardMostMove) {
6913           gameMode = PlayFromGameFile;
6914           ModeHighlight();
6915           SetTrainingModeOff();
6916           DisplayInformation(_("End of game"));
6917         }
6918       } else {
6919         DisplayError(_("Incorrect move"), 0);
6920       }
6921       return 1;
6922     }
6923
6924   /* Ok, now we know that the move is good, so we can kill
6925      the previous line in Analysis Mode */
6926   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6927                                 && currentMove < forwardMostMove) {
6928     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6929     else forwardMostMove = currentMove;
6930   }
6931
6932   ClearMap();
6933
6934   /* If we need the chess program but it's dead, restart it */
6935   ResurrectChessProgram();
6936
6937   /* A user move restarts a paused game*/
6938   if (pausing)
6939     PauseEvent();
6940
6941   thinkOutput[0] = NULLCHAR;
6942
6943   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6944
6945   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6946     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6947     return 1;
6948   }
6949
6950   if (gameMode == BeginningOfGame) {
6951     if (appData.noChessProgram) {
6952       gameMode = EditGame;
6953       SetGameInfo();
6954     } else {
6955       char buf[MSG_SIZ];
6956       gameMode = MachinePlaysBlack;
6957       StartClocks();
6958       SetGameInfo();
6959       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6960       DisplayTitle(buf);
6961       if (first.sendName) {
6962         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6963         SendToProgram(buf, &first);
6964       }
6965       StartClocks();
6966     }
6967     ModeHighlight();
6968   }
6969
6970   /* Relay move to ICS or chess engine */
6971   if (appData.icsActive) {
6972     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6973         gameMode == IcsExamining) {
6974       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6975         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6976         SendToICS("draw ");
6977         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6978       }
6979       // also send plain move, in case ICS does not understand atomic claims
6980       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6981       ics_user_moved = 1;
6982     }
6983   } else {
6984     if (first.sendTime && (gameMode == BeginningOfGame ||
6985                            gameMode == MachinePlaysWhite ||
6986                            gameMode == MachinePlaysBlack)) {
6987       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6988     }
6989     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6990          // [HGM] book: if program might be playing, let it use book
6991         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6992         first.maybeThinking = TRUE;
6993     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6994         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6995         SendBoard(&first, currentMove+1);
6996         if(second.analyzing) {
6997             if(!second.useSetboard) SendToProgram("undo\n", &second);
6998             SendBoard(&second, currentMove+1);
6999         }
7000     } else {
7001         SendMoveToProgram(forwardMostMove-1, &first);
7002         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7003     }
7004     if (currentMove == cmailOldMove + 1) {
7005       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7006     }
7007   }
7008
7009   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7010
7011   switch (gameMode) {
7012   case EditGame:
7013     if(appData.testLegality)
7014     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7015     case MT_NONE:
7016     case MT_CHECK:
7017       break;
7018     case MT_CHECKMATE:
7019     case MT_STAINMATE:
7020       if (WhiteOnMove(currentMove)) {
7021         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7022       } else {
7023         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7024       }
7025       break;
7026     case MT_STALEMATE:
7027       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7028       break;
7029     }
7030     break;
7031
7032   case MachinePlaysBlack:
7033   case MachinePlaysWhite:
7034     /* disable certain menu options while machine is thinking */
7035     SetMachineThinkingEnables();
7036     break;
7037
7038   default:
7039     break;
7040   }
7041
7042   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7043   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7044
7045   if(bookHit) { // [HGM] book: simulate book reply
7046         static char bookMove[MSG_SIZ]; // a bit generous?
7047
7048         programStats.nodes = programStats.depth = programStats.time =
7049         programStats.score = programStats.got_only_move = 0;
7050         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7051
7052         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7053         strcat(bookMove, bookHit);
7054         HandleMachineMove(bookMove, &first);
7055   }
7056   return 1;
7057 }
7058
7059 void
7060 MarkByFEN(char *fen)
7061 {
7062         int r, f;
7063         if(!appData.markers || !appData.highlightDragging) return;
7064         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7065         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7066         while(*fen) {
7067             int s = 0;
7068             marker[r][f] = 0;
7069             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7070             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7071             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7072             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7073             if(*fen == 'T') marker[r][f++] = 0; else
7074             if(*fen == 'Y') marker[r][f++] = 1; else
7075             if(*fen == 'G') marker[r][f++] = 3; else
7076             if(*fen == 'B') marker[r][f++] = 4; else
7077             if(*fen == 'C') marker[r][f++] = 5; else
7078             if(*fen == 'M') marker[r][f++] = 6; else
7079             if(*fen == 'W') marker[r][f++] = 7; else
7080             if(*fen == 'D') marker[r][f++] = 8; else
7081             if(*fen == 'R') marker[r][f++] = 2; else {
7082                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7083               f += s; fen -= s>0;
7084             }
7085             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7086             if(r < 0) break;
7087             fen++;
7088         }
7089         DrawPosition(TRUE, NULL);
7090 }
7091
7092 void
7093 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7094 {
7095     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7096     Markers *m = (Markers *) closure;
7097     if(rf == fromY && ff == fromX)
7098         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7099                          || kind == WhiteCapturesEnPassant
7100                          || kind == BlackCapturesEnPassant);
7101     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7102 }
7103
7104 void
7105 MarkTargetSquares (int clear)
7106 {
7107   int x, y, sum=0;
7108   if(clear) { // no reason to ever suppress clearing
7109     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7110     if(!sum) return; // nothing was cleared,no redraw needed
7111   } else {
7112     int capt = 0;
7113     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7114        !appData.testLegality || gameMode == EditPosition) return;
7115     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7116     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7117       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7118       if(capt)
7119       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7120     }
7121   }
7122   DrawPosition(FALSE, NULL);
7123 }
7124
7125 int
7126 Explode (Board board, int fromX, int fromY, int toX, int toY)
7127 {
7128     if(gameInfo.variant == VariantAtomic &&
7129        (board[toY][toX] != EmptySquare ||                     // capture?
7130         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7131                          board[fromY][fromX] == BlackPawn   )
7132       )) {
7133         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7134         return TRUE;
7135     }
7136     return FALSE;
7137 }
7138
7139 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7140
7141 int
7142 CanPromote (ChessSquare piece, int y)
7143 {
7144         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7145         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7146         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7147            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7148            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7149          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7150         return (piece == BlackPawn && y == 1 ||
7151                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7152                 piece == BlackLance && y == 1 ||
7153                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7154 }
7155
7156 void
7157 HoverEvent (int hiX, int hiY, int x, int y)
7158 {
7159         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7160         int r, f;
7161         if(!first.highlight) return;
7162         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7163           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7164             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7165         else if(hiX != x || hiY != y) {
7166           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7167           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7168             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7169           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7170             char buf[MSG_SIZ];
7171             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7172             SendToProgram(buf, &first);
7173           }
7174           SetHighlights(fromX, fromY, x, y);
7175         }
7176 }
7177
7178 void ReportClick(char *action, int x, int y)
7179 {
7180         char buf[MSG_SIZ]; // Inform engine of what user does
7181         int r, f;
7182         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7183           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7184         if(!first.highlight || gameMode == EditPosition) return;
7185         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7186         SendToProgram(buf, &first);
7187 }
7188
7189 void
7190 LeftClick (ClickType clickType, int xPix, int yPix)
7191 {
7192     int x, y;
7193     Boolean saveAnimate;
7194     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7195     char promoChoice = NULLCHAR;
7196     ChessSquare piece;
7197     static TimeMark lastClickTime, prevClickTime;
7198
7199     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7200
7201     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7202
7203     if (clickType == Press) ErrorPopDown();
7204     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7205
7206     x = EventToSquare(xPix, BOARD_WIDTH);
7207     y = EventToSquare(yPix, BOARD_HEIGHT);
7208     if (!flipView && y >= 0) {
7209         y = BOARD_HEIGHT - 1 - y;
7210     }
7211     if (flipView && x >= 0) {
7212         x = BOARD_WIDTH - 1 - x;
7213     }
7214
7215     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7216         defaultPromoChoice = promoSweep;
7217         promoSweep = EmptySquare;   // terminate sweep
7218         promoDefaultAltered = TRUE;
7219         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7220     }
7221
7222     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7223         if(clickType == Release) return; // ignore upclick of click-click destination
7224         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7225         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7226         if(gameInfo.holdingsWidth &&
7227                 (WhiteOnMove(currentMove)
7228                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7229                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7230             // click in right holdings, for determining promotion piece
7231             ChessSquare p = boards[currentMove][y][x];
7232             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7233             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7234             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7235                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7236                 fromX = fromY = -1;
7237                 return;
7238             }
7239         }
7240         DrawPosition(FALSE, boards[currentMove]);
7241         return;
7242     }
7243
7244     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7245     if(clickType == Press
7246             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7247               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7248               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7249         return;
7250
7251     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7252         // could be static click on premove from-square: abort premove
7253         gotPremove = 0;
7254         ClearPremoveHighlights();
7255     }
7256
7257     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7258         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7259
7260     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7261         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7262                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7263         defaultPromoChoice = DefaultPromoChoice(side);
7264     }
7265
7266     autoQueen = appData.alwaysPromoteToQueen;
7267
7268     if (fromX == -1) {
7269       int originalY = y;
7270       gatingPiece = EmptySquare;
7271       if (clickType != Press) {
7272         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7273             DragPieceEnd(xPix, yPix); dragging = 0;
7274             DrawPosition(FALSE, NULL);
7275         }
7276         return;
7277       }
7278       doubleClick = FALSE;
7279       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7280         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7281       }
7282       fromX = x; fromY = y; toX = toY = -1;
7283       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7284          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7285          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7286             /* First square */
7287             if (OKToStartUserMove(fromX, fromY)) {
7288                 second = 0;
7289                 ReportClick("lift", x, y);
7290                 MarkTargetSquares(0);
7291                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7292                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7293                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7294                     promoSweep = defaultPromoChoice;
7295                     selectFlag = 0; lastX = xPix; lastY = yPix;
7296                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7297                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7298                 }
7299                 if (appData.highlightDragging) {
7300                     SetHighlights(fromX, fromY, -1, -1);
7301                 } else {
7302                     ClearHighlights();
7303                 }
7304             } else fromX = fromY = -1;
7305             return;
7306         }
7307     }
7308
7309     /* fromX != -1 */
7310     if (clickType == Press && gameMode != EditPosition) {
7311         ChessSquare fromP;
7312         ChessSquare toP;
7313         int frc;
7314
7315         // ignore off-board to clicks
7316         if(y < 0 || x < 0) return;
7317
7318         /* Check if clicking again on the same color piece */
7319         fromP = boards[currentMove][fromY][fromX];
7320         toP = boards[currentMove][y][x];
7321         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7322         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7323              WhitePawn <= toP && toP <= WhiteKing &&
7324              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7325              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7326             (BlackPawn <= fromP && fromP <= BlackKing &&
7327              BlackPawn <= toP && toP <= BlackKing &&
7328              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7329              !(fromP == BlackKing && toP == BlackRook && frc))) {
7330             /* Clicked again on same color piece -- changed his mind */
7331             second = (x == fromX && y == fromY);
7332             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7333                 second = FALSE; // first double-click rather than scond click
7334                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7335             }
7336             promoDefaultAltered = FALSE;
7337             MarkTargetSquares(1);
7338            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7339             if (appData.highlightDragging) {
7340                 SetHighlights(x, y, -1, -1);
7341             } else {
7342                 ClearHighlights();
7343             }
7344             if (OKToStartUserMove(x, y)) {
7345                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7346                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7347                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7348                  gatingPiece = boards[currentMove][fromY][fromX];
7349                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7350                 fromX = x;
7351                 fromY = y; dragging = 1;
7352                 ReportClick("lift", x, y);
7353                 MarkTargetSquares(0);
7354                 DragPieceBegin(xPix, yPix, FALSE);
7355                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7356                     promoSweep = defaultPromoChoice;
7357                     selectFlag = 0; lastX = xPix; lastY = yPix;
7358                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7359                 }
7360             }
7361            }
7362            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7363            second = FALSE;
7364         }
7365         // ignore clicks on holdings
7366         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7367     }
7368
7369     if (clickType == Release && x == fromX && y == fromY) {
7370         DragPieceEnd(xPix, yPix); dragging = 0;
7371         if(clearFlag) {
7372             // a deferred attempt to click-click move an empty square on top of a piece
7373             boards[currentMove][y][x] = EmptySquare;
7374             ClearHighlights();
7375             DrawPosition(FALSE, boards[currentMove]);
7376             fromX = fromY = -1; clearFlag = 0;
7377             return;
7378         }
7379         if (appData.animateDragging) {
7380             /* Undo animation damage if any */
7381             DrawPosition(FALSE, NULL);
7382         }
7383         if (second || sweepSelecting) {
7384             /* Second up/down in same square; just abort move */
7385             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7386             second = sweepSelecting = 0;
7387             fromX = fromY = -1;
7388             gatingPiece = EmptySquare;
7389             MarkTargetSquares(1);
7390             ClearHighlights();
7391             gotPremove = 0;
7392             ClearPremoveHighlights();
7393         } else {
7394             /* First upclick in same square; start click-click mode */
7395             SetHighlights(x, y, -1, -1);
7396         }
7397         return;
7398     }
7399
7400     clearFlag = 0;
7401
7402     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7403         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7404         DisplayMessage(_("only marked squares are legal"),"");
7405         DrawPosition(TRUE, NULL);
7406         return; // ignore to-click
7407     }
7408
7409     /* we now have a different from- and (possibly off-board) to-square */
7410     /* Completed move */
7411     if(!sweepSelecting) {
7412         toX = x;
7413         toY = y;
7414     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7415
7416     saveAnimate = appData.animate;
7417     if (clickType == Press) {
7418         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7419             // must be Edit Position mode with empty-square selected
7420             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7421             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7422             return;
7423         }
7424         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7425           if(appData.sweepSelect) {
7426             ChessSquare piece = boards[currentMove][fromY][fromX];
7427             promoSweep = defaultPromoChoice;
7428             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7429             selectFlag = 0; lastX = xPix; lastY = yPix;
7430             Sweep(0); // Pawn that is going to promote: preview promotion piece
7431             sweepSelecting = 1;
7432             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7433             MarkTargetSquares(1);
7434           }
7435           return; // promo popup appears on up-click
7436         }
7437         /* Finish clickclick move */
7438         if (appData.animate || appData.highlightLastMove) {
7439             SetHighlights(fromX, fromY, toX, toY);
7440         } else {
7441             ClearHighlights();
7442         }
7443     } else {
7444 #if 0
7445 // [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
7446         /* Finish drag move */
7447         if (appData.highlightLastMove) {
7448             SetHighlights(fromX, fromY, toX, toY);
7449         } else {
7450             ClearHighlights();
7451         }
7452 #endif
7453         DragPieceEnd(xPix, yPix); dragging = 0;
7454         /* Don't animate move and drag both */
7455         appData.animate = FALSE;
7456     }
7457
7458     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7459     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7460         ChessSquare piece = boards[currentMove][fromY][fromX];
7461         if(gameMode == EditPosition && piece != EmptySquare &&
7462            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7463             int n;
7464
7465             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7466                 n = PieceToNumber(piece - (int)BlackPawn);
7467                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7468                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7469                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7470             } else
7471             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7472                 n = PieceToNumber(piece);
7473                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7474                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7475                 boards[currentMove][n][BOARD_WIDTH-2]++;
7476             }
7477             boards[currentMove][fromY][fromX] = EmptySquare;
7478         }
7479         ClearHighlights();
7480         fromX = fromY = -1;
7481         MarkTargetSquares(1);
7482         DrawPosition(TRUE, boards[currentMove]);
7483         return;
7484     }
7485
7486     // off-board moves should not be highlighted
7487     if(x < 0 || y < 0) ClearHighlights();
7488     else ReportClick("put", x, y);
7489
7490     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7491
7492     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7493         SetHighlights(fromX, fromY, toX, toY);
7494         MarkTargetSquares(1);
7495         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7496             // [HGM] super: promotion to captured piece selected from holdings
7497             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7498             promotionChoice = TRUE;
7499             // kludge follows to temporarily execute move on display, without promoting yet
7500             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7501             boards[currentMove][toY][toX] = p;
7502             DrawPosition(FALSE, boards[currentMove]);
7503             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7504             boards[currentMove][toY][toX] = q;
7505             DisplayMessage("Click in holdings to choose piece", "");
7506             return;
7507         }
7508         PromotionPopUp();
7509     } else {
7510         int oldMove = currentMove;
7511         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7512         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7513         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7514         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7515            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7516             DrawPosition(TRUE, boards[currentMove]);
7517         MarkTargetSquares(1);
7518         fromX = fromY = -1;
7519     }
7520     appData.animate = saveAnimate;
7521     if (appData.animate || appData.animateDragging) {
7522         /* Undo animation damage if needed */
7523         DrawPosition(FALSE, NULL);
7524     }
7525 }
7526
7527 int
7528 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7529 {   // front-end-free part taken out of PieceMenuPopup
7530     int whichMenu; int xSqr, ySqr;
7531
7532     if(seekGraphUp) { // [HGM] seekgraph
7533         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7534         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7535         return -2;
7536     }
7537
7538     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7539          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7540         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7541         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7542         if(action == Press)   {
7543             originalFlip = flipView;
7544             flipView = !flipView; // temporarily flip board to see game from partners perspective
7545             DrawPosition(TRUE, partnerBoard);
7546             DisplayMessage(partnerStatus, "");
7547             partnerUp = TRUE;
7548         } else if(action == Release) {
7549             flipView = originalFlip;
7550             DrawPosition(TRUE, boards[currentMove]);
7551             partnerUp = FALSE;
7552         }
7553         return -2;
7554     }
7555
7556     xSqr = EventToSquare(x, BOARD_WIDTH);
7557     ySqr = EventToSquare(y, BOARD_HEIGHT);
7558     if (action == Release) {
7559         if(pieceSweep != EmptySquare) {
7560             EditPositionMenuEvent(pieceSweep, toX, toY);
7561             pieceSweep = EmptySquare;
7562         } else UnLoadPV(); // [HGM] pv
7563     }
7564     if (action != Press) return -2; // return code to be ignored
7565     switch (gameMode) {
7566       case IcsExamining:
7567         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7568       case EditPosition:
7569         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7570         if (xSqr < 0 || ySqr < 0) return -1;
7571         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7572         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7573         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7574         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7575         NextPiece(0);
7576         return 2; // grab
7577       case IcsObserving:
7578         if(!appData.icsEngineAnalyze) return -1;
7579       case IcsPlayingWhite:
7580       case IcsPlayingBlack:
7581         if(!appData.zippyPlay) goto noZip;
7582       case AnalyzeMode:
7583       case AnalyzeFile:
7584       case MachinePlaysWhite:
7585       case MachinePlaysBlack:
7586       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7587         if (!appData.dropMenu) {
7588           LoadPV(x, y);
7589           return 2; // flag front-end to grab mouse events
7590         }
7591         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7592            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7593       case EditGame:
7594       noZip:
7595         if (xSqr < 0 || ySqr < 0) return -1;
7596         if (!appData.dropMenu || appData.testLegality &&
7597             gameInfo.variant != VariantBughouse &&
7598             gameInfo.variant != VariantCrazyhouse) return -1;
7599         whichMenu = 1; // drop menu
7600         break;
7601       default:
7602         return -1;
7603     }
7604
7605     if (((*fromX = xSqr) < 0) ||
7606         ((*fromY = ySqr) < 0)) {
7607         *fromX = *fromY = -1;
7608         return -1;
7609     }
7610     if (flipView)
7611       *fromX = BOARD_WIDTH - 1 - *fromX;
7612     else
7613       *fromY = BOARD_HEIGHT - 1 - *fromY;
7614
7615     return whichMenu;
7616 }
7617
7618 void
7619 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7620 {
7621 //    char * hint = lastHint;
7622     FrontEndProgramStats stats;
7623
7624     stats.which = cps == &first ? 0 : 1;
7625     stats.depth = cpstats->depth;
7626     stats.nodes = cpstats->nodes;
7627     stats.score = cpstats->score;
7628     stats.time = cpstats->time;
7629     stats.pv = cpstats->movelist;
7630     stats.hint = lastHint;
7631     stats.an_move_index = 0;
7632     stats.an_move_count = 0;
7633
7634     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7635         stats.hint = cpstats->move_name;
7636         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7637         stats.an_move_count = cpstats->nr_moves;
7638     }
7639
7640     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
7641
7642     SetProgramStats( &stats );
7643 }
7644
7645 void
7646 ClearEngineOutputPane (int which)
7647 {
7648     static FrontEndProgramStats dummyStats;
7649     dummyStats.which = which;
7650     dummyStats.pv = "#";
7651     SetProgramStats( &dummyStats );
7652 }
7653
7654 #define MAXPLAYERS 500
7655
7656 char *
7657 TourneyStandings (int display)
7658 {
7659     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7660     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7661     char result, *p, *names[MAXPLAYERS];
7662
7663     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7664         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7665     names[0] = p = strdup(appData.participants);
7666     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7667
7668     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7669
7670     while(result = appData.results[nr]) {
7671         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7672         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7673         wScore = bScore = 0;
7674         switch(result) {
7675           case '+': wScore = 2; break;
7676           case '-': bScore = 2; break;
7677           case '=': wScore = bScore = 1; break;
7678           case ' ':
7679           case '*': return strdup("busy"); // tourney not finished
7680         }
7681         score[w] += wScore;
7682         score[b] += bScore;
7683         games[w]++;
7684         games[b]++;
7685         nr++;
7686     }
7687     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7688     for(w=0; w<nPlayers; w++) {
7689         bScore = -1;
7690         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7691         ranking[w] = b; points[w] = bScore; score[b] = -2;
7692     }
7693     p = malloc(nPlayers*34+1);
7694     for(w=0; w<nPlayers && w<display; w++)
7695         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7696     free(names[0]);
7697     return p;
7698 }
7699
7700 void
7701 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7702 {       // count all piece types
7703         int p, f, r;
7704         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7705         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7706         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7707                 p = board[r][f];
7708                 pCnt[p]++;
7709                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7710                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7711                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7712                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7713                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7714                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7715         }
7716 }
7717
7718 int
7719 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7720 {
7721         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7722         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7723
7724         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7725         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7726         if(myPawns == 2 && nMine == 3) // KPP
7727             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7728         if(myPawns == 1 && nMine == 2) // KP
7729             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7730         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7731             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7732         if(myPawns) return FALSE;
7733         if(pCnt[WhiteRook+side])
7734             return pCnt[BlackRook-side] ||
7735                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7736                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7737                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7738         if(pCnt[WhiteCannon+side]) {
7739             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7740             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7741         }
7742         if(pCnt[WhiteKnight+side])
7743             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7744         return FALSE;
7745 }
7746
7747 int
7748 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7749 {
7750         VariantClass v = gameInfo.variant;
7751
7752         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7753         if(v == VariantShatranj) return TRUE; // always winnable through baring
7754         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7755         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7756
7757         if(v == VariantXiangqi) {
7758                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7759
7760                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7761                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7762                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7763                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7764                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7765                 if(stale) // we have at least one last-rank P plus perhaps C
7766                     return majors // KPKX
7767                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7768                 else // KCA*E*
7769                     return pCnt[WhiteFerz+side] // KCAK
7770                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7771                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7772                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7773
7774         } else if(v == VariantKnightmate) {
7775                 if(nMine == 1) return FALSE;
7776                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7777         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7778                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7779
7780                 if(nMine == 1) return FALSE; // bare King
7781                 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
7782                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7783                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7784                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7785                 if(pCnt[WhiteKnight+side])
7786                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7787                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7788                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7789                 if(nBishops)
7790                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7791                 if(pCnt[WhiteAlfil+side])
7792                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7793                 if(pCnt[WhiteWazir+side])
7794                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7795         }
7796
7797         return TRUE;
7798 }
7799
7800 int
7801 CompareWithRights (Board b1, Board b2)
7802 {
7803     int rights = 0;
7804     if(!CompareBoards(b1, b2)) return FALSE;
7805     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7806     /* compare castling rights */
7807     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7808            rights++; /* King lost rights, while rook still had them */
7809     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7810         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7811            rights++; /* but at least one rook lost them */
7812     }
7813     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7814            rights++;
7815     if( b1[CASTLING][5] != NoRights ) {
7816         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7817            rights++;
7818     }
7819     return rights == 0;
7820 }
7821
7822 int
7823 Adjudicate (ChessProgramState *cps)
7824 {       // [HGM] some adjudications useful with buggy engines
7825         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7826         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7827         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7828         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7829         int k, drop, count = 0; static int bare = 1;
7830         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7831         Boolean canAdjudicate = !appData.icsActive;
7832
7833         // most tests only when we understand the game, i.e. legality-checking on
7834             if( appData.testLegality )
7835             {   /* [HGM] Some more adjudications for obstinate engines */
7836                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7837                 static int moveCount = 6;
7838                 ChessMove result;
7839                 char *reason = NULL;
7840
7841                 /* Count what is on board. */
7842                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7843
7844                 /* Some material-based adjudications that have to be made before stalemate test */
7845                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7846                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7847                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7848                      if(canAdjudicate && appData.checkMates) {
7849                          if(engineOpponent)
7850                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7851                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7852                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7853                          return 1;
7854                      }
7855                 }
7856
7857                 /* Bare King in Shatranj (loses) or Losers (wins) */
7858                 if( nrW == 1 || nrB == 1) {
7859                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7860                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7861                      if(canAdjudicate && appData.checkMates) {
7862                          if(engineOpponent)
7863                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7864                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7865                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7866                          return 1;
7867                      }
7868                   } else
7869                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7870                   {    /* bare King */
7871                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7872                         if(canAdjudicate && appData.checkMates) {
7873                             /* but only adjudicate if adjudication enabled */
7874                             if(engineOpponent)
7875                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7876                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7877                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7878                             return 1;
7879                         }
7880                   }
7881                 } else bare = 1;
7882
7883
7884             // don't wait for engine to announce game end if we can judge ourselves
7885             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7886               case MT_CHECK:
7887                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7888                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7889                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7890                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7891                             checkCnt++;
7892                         if(checkCnt >= 2) {
7893                             reason = "Xboard adjudication: 3rd check";
7894                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7895                             break;
7896                         }
7897                     }
7898                 }
7899               case MT_NONE:
7900               default:
7901                 break;
7902               case MT_STALEMATE:
7903               case MT_STAINMATE:
7904                 reason = "Xboard adjudication: Stalemate";
7905                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7906                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7907                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7908                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7909                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7910                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7911                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7912                                                                         EP_CHECKMATE : EP_WINS);
7913                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7914                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7915                 }
7916                 break;
7917               case MT_CHECKMATE:
7918                 reason = "Xboard adjudication: Checkmate";
7919                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7920                 if(gameInfo.variant == VariantShogi) {
7921                     if(forwardMostMove > backwardMostMove
7922                        && moveList[forwardMostMove-1][1] == '@'
7923                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7924                         reason = "XBoard adjudication: pawn-drop mate";
7925                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7926                     }
7927                 }
7928                 break;
7929             }
7930
7931                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7932                     case EP_STALEMATE:
7933                         result = GameIsDrawn; break;
7934                     case EP_CHECKMATE:
7935                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7936                     case EP_WINS:
7937                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7938                     default:
7939                         result = EndOfFile;
7940                 }
7941                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7942                     if(engineOpponent)
7943                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7944                     GameEnds( result, reason, GE_XBOARD );
7945                     return 1;
7946                 }
7947
7948                 /* Next absolutely insufficient mating material. */
7949                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7950                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7951                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7952
7953                      /* always flag draws, for judging claims */
7954                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7955
7956                      if(canAdjudicate && appData.materialDraws) {
7957                          /* but only adjudicate them if adjudication enabled */
7958                          if(engineOpponent) {
7959                            SendToProgram("force\n", engineOpponent); // suppress reply
7960                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7961                          }
7962                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7963                          return 1;
7964                      }
7965                 }
7966
7967                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7968                 if(gameInfo.variant == VariantXiangqi ?
7969                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7970                  : nrW + nrB == 4 &&
7971                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7972                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7973                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7974                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7975                    ) ) {
7976                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7977                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7978                           if(engineOpponent) {
7979                             SendToProgram("force\n", engineOpponent); // suppress reply
7980                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7981                           }
7982                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7983                           return 1;
7984                      }
7985                 } else moveCount = 6;
7986             }
7987
7988         // Repetition draws and 50-move rule can be applied independently of legality testing
7989
7990                 /* Check for rep-draws */
7991                 count = 0;
7992                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7993                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7994                 for(k = forwardMostMove-2;
7995                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7996                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7997                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7998                     k-=2)
7999                 {   int rights=0;
8000                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8001                         /* compare castling rights */
8002                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8003                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8004                                 rights++; /* King lost rights, while rook still had them */
8005                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8006                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8007                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8008                                    rights++; /* but at least one rook lost them */
8009                         }
8010                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8011                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8012                                 rights++;
8013                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8014                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8015                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8016                                    rights++;
8017                         }
8018                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8019                             && appData.drawRepeats > 1) {
8020                              /* adjudicate after user-specified nr of repeats */
8021                              int result = GameIsDrawn;
8022                              char *details = "XBoard adjudication: repetition draw";
8023                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8024                                 // [HGM] xiangqi: check for forbidden perpetuals
8025                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8026                                 for(m=forwardMostMove; m>k; m-=2) {
8027                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8028                                         ourPerpetual = 0; // the current mover did not always check
8029                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8030                                         hisPerpetual = 0; // the opponent did not always check
8031                                 }
8032                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8033                                                                         ourPerpetual, hisPerpetual);
8034                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8035                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8036                                     details = "Xboard adjudication: perpetual checking";
8037                                 } else
8038                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8039                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8040                                 } else
8041                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8042                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8043                                         result = BlackWins;
8044                                         details = "Xboard adjudication: repetition";
8045                                     }
8046                                 } else // it must be XQ
8047                                 // Now check for perpetual chases
8048                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8049                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8050                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8051                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8052                                         static char resdet[MSG_SIZ];
8053                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8054                                         details = resdet;
8055                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8056                                     } else
8057                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8058                                         break; // Abort repetition-checking loop.
8059                                 }
8060                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8061                              }
8062                              if(engineOpponent) {
8063                                SendToProgram("force\n", engineOpponent); // suppress reply
8064                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8065                              }
8066                              GameEnds( result, details, GE_XBOARD );
8067                              return 1;
8068                         }
8069                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8070                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8071                     }
8072                 }
8073
8074                 /* Now we test for 50-move draws. Determine ply count */
8075                 count = forwardMostMove;
8076                 /* look for last irreversble move */
8077                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8078                     count--;
8079                 /* if we hit starting position, add initial plies */
8080                 if( count == backwardMostMove )
8081                     count -= initialRulePlies;
8082                 count = forwardMostMove - count;
8083                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8084                         // adjust reversible move counter for checks in Xiangqi
8085                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8086                         if(i < backwardMostMove) i = backwardMostMove;
8087                         while(i <= forwardMostMove) {
8088                                 lastCheck = inCheck; // check evasion does not count
8089                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8090                                 if(inCheck || lastCheck) count--; // check does not count
8091                                 i++;
8092                         }
8093                 }
8094                 if( count >= 100)
8095                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8096                          /* this is used to judge if draw claims are legal */
8097                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8098                          if(engineOpponent) {
8099                            SendToProgram("force\n", engineOpponent); // suppress reply
8100                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8101                          }
8102                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8103                          return 1;
8104                 }
8105
8106                 /* if draw offer is pending, treat it as a draw claim
8107                  * when draw condition present, to allow engines a way to
8108                  * claim draws before making their move to avoid a race
8109                  * condition occurring after their move
8110                  */
8111                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8112                          char *p = NULL;
8113                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8114                              p = "Draw claim: 50-move rule";
8115                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8116                              p = "Draw claim: 3-fold repetition";
8117                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8118                              p = "Draw claim: insufficient mating material";
8119                          if( p != NULL && canAdjudicate) {
8120                              if(engineOpponent) {
8121                                SendToProgram("force\n", engineOpponent); // suppress reply
8122                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8123                              }
8124                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8125                              return 1;
8126                          }
8127                 }
8128
8129                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8130                     if(engineOpponent) {
8131                       SendToProgram("force\n", engineOpponent); // suppress reply
8132                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8133                     }
8134                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8135                     return 1;
8136                 }
8137         return 0;
8138 }
8139
8140 char *
8141 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8142 {   // [HGM] book: this routine intercepts moves to simulate book replies
8143     char *bookHit = NULL;
8144
8145     //first determine if the incoming move brings opponent into his book
8146     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8147         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8148     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8149     if(bookHit != NULL && !cps->bookSuspend) {
8150         // make sure opponent is not going to reply after receiving move to book position
8151         SendToProgram("force\n", cps);
8152         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8153     }
8154     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8155     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8156     // now arrange restart after book miss
8157     if(bookHit) {
8158         // after a book hit we never send 'go', and the code after the call to this routine
8159         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8160         char buf[MSG_SIZ], *move = bookHit;
8161         if(cps->useSAN) {
8162             int fromX, fromY, toX, toY;
8163             char promoChar;
8164             ChessMove moveType;
8165             move = buf + 30;
8166             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8167                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8168                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8169                                     PosFlags(forwardMostMove),
8170                                     fromY, fromX, toY, toX, promoChar, move);
8171             } else {
8172                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8173                 bookHit = NULL;
8174             }
8175         }
8176         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8177         SendToProgram(buf, cps);
8178         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8179     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8180         SendToProgram("go\n", cps);
8181         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8182     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8183         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8184             SendToProgram("go\n", cps);
8185         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8186     }
8187     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8188 }
8189
8190 int
8191 LoadError (char *errmess, ChessProgramState *cps)
8192 {   // unloads engine and switches back to -ncp mode if it was first
8193     if(cps->initDone) return FALSE;
8194     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8195     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8196     cps->pr = NoProc;
8197     if(cps == &first) {
8198         appData.noChessProgram = TRUE;
8199         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8200         gameMode = BeginningOfGame; ModeHighlight();
8201         SetNCPMode();
8202     }
8203     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8204     DisplayMessage("", ""); // erase waiting message
8205     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8206     return TRUE;
8207 }
8208
8209 char *savedMessage;
8210 ChessProgramState *savedState;
8211 void
8212 DeferredBookMove (void)
8213 {
8214         if(savedState->lastPing != savedState->lastPong)
8215                     ScheduleDelayedEvent(DeferredBookMove, 10);
8216         else
8217         HandleMachineMove(savedMessage, savedState);
8218 }
8219
8220 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8221 static ChessProgramState *stalledEngine;
8222 static char stashedInputMove[MSG_SIZ];
8223
8224 void
8225 HandleMachineMove (char *message, ChessProgramState *cps)
8226 {
8227     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8228     char realname[MSG_SIZ];
8229     int fromX, fromY, toX, toY;
8230     ChessMove moveType;
8231     char promoChar;
8232     char *p, *pv=buf1;
8233     int machineWhite, oldError;
8234     char *bookHit;
8235
8236     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8237         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8238         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8239             DisplayError(_("Invalid pairing from pairing engine"), 0);
8240             return;
8241         }
8242         pairingReceived = 1;
8243         NextMatchGame();
8244         return; // Skim the pairing messages here.
8245     }
8246
8247     oldError = cps->userError; cps->userError = 0;
8248
8249 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8250     /*
8251      * Kludge to ignore BEL characters
8252      */
8253     while (*message == '\007') message++;
8254
8255     /*
8256      * [HGM] engine debug message: ignore lines starting with '#' character
8257      */
8258     if(cps->debug && *message == '#') return;
8259
8260     /*
8261      * Look for book output
8262      */
8263     if (cps == &first && bookRequested) {
8264         if (message[0] == '\t' || message[0] == ' ') {
8265             /* Part of the book output is here; append it */
8266             strcat(bookOutput, message);
8267             strcat(bookOutput, "  \n");
8268             return;
8269         } else if (bookOutput[0] != NULLCHAR) {
8270             /* All of book output has arrived; display it */
8271             char *p = bookOutput;
8272             while (*p != NULLCHAR) {
8273                 if (*p == '\t') *p = ' ';
8274                 p++;
8275             }
8276             DisplayInformation(bookOutput);
8277             bookRequested = FALSE;
8278             /* Fall through to parse the current output */
8279         }
8280     }
8281
8282     /*
8283      * Look for machine move.
8284      */
8285     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8286         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8287     {
8288         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8289             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8290             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8291             stalledEngine = cps;
8292             if(appData.ponderNextMove) { // bring opponent out of ponder
8293                 if(gameMode == TwoMachinesPlay) {
8294                     if(cps->other->pause)
8295                         PauseEngine(cps->other);
8296                     else
8297                         SendToProgram("easy\n", cps->other);
8298                 }
8299             }
8300             StopClocks();
8301             return;
8302         }
8303
8304         /* This method is only useful on engines that support ping */
8305         if (cps->lastPing != cps->lastPong) {
8306           if (gameMode == BeginningOfGame) {
8307             /* Extra move from before last new; ignore */
8308             if (appData.debugMode) {
8309                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8310             }
8311           } else {
8312             if (appData.debugMode) {
8313                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8314                         cps->which, gameMode);
8315             }
8316
8317             SendToProgram("undo\n", cps);
8318           }
8319           return;
8320         }
8321
8322         switch (gameMode) {
8323           case BeginningOfGame:
8324             /* Extra move from before last reset; ignore */
8325             if (appData.debugMode) {
8326                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8327             }
8328             return;
8329
8330           case EndOfGame:
8331           case IcsIdle:
8332           default:
8333             /* Extra move after we tried to stop.  The mode test is
8334                not a reliable way of detecting this problem, but it's
8335                the best we can do on engines that don't support ping.
8336             */
8337             if (appData.debugMode) {
8338                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8339                         cps->which, gameMode);
8340             }
8341             SendToProgram("undo\n", cps);
8342             return;
8343
8344           case MachinePlaysWhite:
8345           case IcsPlayingWhite:
8346             machineWhite = TRUE;
8347             break;
8348
8349           case MachinePlaysBlack:
8350           case IcsPlayingBlack:
8351             machineWhite = FALSE;
8352             break;
8353
8354           case TwoMachinesPlay:
8355             machineWhite = (cps->twoMachinesColor[0] == 'w');
8356             break;
8357         }
8358         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8359             if (appData.debugMode) {
8360                 fprintf(debugFP,
8361                         "Ignoring move out of turn by %s, gameMode %d"
8362                         ", forwardMost %d\n",
8363                         cps->which, gameMode, forwardMostMove);
8364             }
8365             return;
8366         }
8367
8368         if(cps->alphaRank) AlphaRank(machineMove, 4);
8369         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8370                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8371             /* Machine move could not be parsed; ignore it. */
8372           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8373                     machineMove, _(cps->which));
8374             DisplayMoveError(buf1);
8375             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8376                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8377             if (gameMode == TwoMachinesPlay) {
8378               GameEnds(machineWhite ? BlackWins : WhiteWins,
8379                        buf1, GE_XBOARD);
8380             }
8381             return;
8382         }
8383
8384         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8385         /* So we have to redo legality test with true e.p. status here,  */
8386         /* to make sure an illegal e.p. capture does not slip through,   */
8387         /* to cause a forfeit on a justified illegal-move complaint      */
8388         /* of the opponent.                                              */
8389         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8390            ChessMove moveType;
8391            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8392                              fromY, fromX, toY, toX, promoChar);
8393             if(moveType == IllegalMove) {
8394               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8395                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8396                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8397                            buf1, GE_XBOARD);
8398                 return;
8399            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8400            /* [HGM] Kludge to handle engines that send FRC-style castling
8401               when they shouldn't (like TSCP-Gothic) */
8402            switch(moveType) {
8403              case WhiteASideCastleFR:
8404              case BlackASideCastleFR:
8405                toX+=2;
8406                currentMoveString[2]++;
8407                break;
8408              case WhiteHSideCastleFR:
8409              case BlackHSideCastleFR:
8410                toX--;
8411                currentMoveString[2]--;
8412                break;
8413              default: ; // nothing to do, but suppresses warning of pedantic compilers
8414            }
8415         }
8416         hintRequested = FALSE;
8417         lastHint[0] = NULLCHAR;
8418         bookRequested = FALSE;
8419         /* Program may be pondering now */
8420         cps->maybeThinking = TRUE;
8421         if (cps->sendTime == 2) cps->sendTime = 1;
8422         if (cps->offeredDraw) cps->offeredDraw--;
8423
8424         /* [AS] Save move info*/
8425         pvInfoList[ forwardMostMove ].score = programStats.score;
8426         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8427         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8428
8429         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8430
8431         /* Test suites abort the 'game' after one move */
8432         if(*appData.finger) {
8433            static FILE *f;
8434            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8435            if(!f) f = fopen(appData.finger, "w");
8436            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8437            else { DisplayFatalError("Bad output file", errno, 0); return; }
8438            free(fen);
8439            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8440         }
8441
8442         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8443         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8444             int count = 0;
8445
8446             while( count < adjudicateLossPlies ) {
8447                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8448
8449                 if( count & 1 ) {
8450                     score = -score; /* Flip score for winning side */
8451                 }
8452
8453                 if( score > adjudicateLossThreshold ) {
8454                     break;
8455                 }
8456
8457                 count++;
8458             }
8459
8460             if( count >= adjudicateLossPlies ) {
8461                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8462
8463                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8464                     "Xboard adjudication",
8465                     GE_XBOARD );
8466
8467                 return;
8468             }
8469         }
8470
8471         if(Adjudicate(cps)) {
8472             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8473             return; // [HGM] adjudicate: for all automatic game ends
8474         }
8475
8476 #if ZIPPY
8477         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8478             first.initDone) {
8479           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8480                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8481                 SendToICS("draw ");
8482                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8483           }
8484           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8485           ics_user_moved = 1;
8486           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8487                 char buf[3*MSG_SIZ];
8488
8489                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8490                         programStats.score / 100.,
8491                         programStats.depth,
8492                         programStats.time / 100.,
8493                         (unsigned int)programStats.nodes,
8494                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8495                         programStats.movelist);
8496                 SendToICS(buf);
8497 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8498           }
8499         }
8500 #endif
8501
8502         /* [AS] Clear stats for next move */
8503         ClearProgramStats();
8504         thinkOutput[0] = NULLCHAR;
8505         hiddenThinkOutputState = 0;
8506
8507         bookHit = NULL;
8508         if (gameMode == TwoMachinesPlay) {
8509             /* [HGM] relaying draw offers moved to after reception of move */
8510             /* and interpreting offer as claim if it brings draw condition */
8511             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8512                 SendToProgram("draw\n", cps->other);
8513             }
8514             if (cps->other->sendTime) {
8515                 SendTimeRemaining(cps->other,
8516                                   cps->other->twoMachinesColor[0] == 'w');
8517             }
8518             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8519             if (firstMove && !bookHit) {
8520                 firstMove = FALSE;
8521                 if (cps->other->useColors) {
8522                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8523                 }
8524                 SendToProgram("go\n", cps->other);
8525             }
8526             cps->other->maybeThinking = TRUE;
8527         }
8528
8529         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8530
8531         if (!pausing && appData.ringBellAfterMoves) {
8532             RingBell();
8533         }
8534
8535         /*
8536          * Reenable menu items that were disabled while
8537          * machine was thinking
8538          */
8539         if (gameMode != TwoMachinesPlay)
8540             SetUserThinkingEnables();
8541
8542         // [HGM] book: after book hit opponent has received move and is now in force mode
8543         // force the book reply into it, and then fake that it outputted this move by jumping
8544         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8545         if(bookHit) {
8546                 static char bookMove[MSG_SIZ]; // a bit generous?
8547
8548                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8549                 strcat(bookMove, bookHit);
8550                 message = bookMove;
8551                 cps = cps->other;
8552                 programStats.nodes = programStats.depth = programStats.time =
8553                 programStats.score = programStats.got_only_move = 0;
8554                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8555
8556                 if(cps->lastPing != cps->lastPong) {
8557                     savedMessage = message; // args for deferred call
8558                     savedState = cps;
8559                     ScheduleDelayedEvent(DeferredBookMove, 10);
8560                     return;
8561                 }
8562                 goto FakeBookMove;
8563         }
8564
8565         return;
8566     }
8567
8568     /* Set special modes for chess engines.  Later something general
8569      *  could be added here; for now there is just one kludge feature,
8570      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8571      *  when "xboard" is given as an interactive command.
8572      */
8573     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8574         cps->useSigint = FALSE;
8575         cps->useSigterm = FALSE;
8576     }
8577     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8578       ParseFeatures(message+8, cps);
8579       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8580     }
8581
8582     if (!strncmp(message, "setup ", 6) && 
8583         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8584                                         ) { // [HGM] allow first engine to define opening position
8585       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8586       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8587       *buf = NULLCHAR;
8588       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8589       if(startedFromSetupPosition) return;
8590       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8591       if(dummy >= 3) {
8592         while(message[s] && message[s++] != ' ');
8593         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8594            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8595             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8596             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8597           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8598           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8599         }
8600       }
8601       ParseFEN(boards[0], &dummy, message+s, FALSE);
8602       DrawPosition(TRUE, boards[0]);
8603       startedFromSetupPosition = TRUE;
8604       return;
8605     }
8606     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8607      * want this, I was asked to put it in, and obliged.
8608      */
8609     if (!strncmp(message, "setboard ", 9)) {
8610         Board initial_position;
8611
8612         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8613
8614         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8615             DisplayError(_("Bad FEN received from engine"), 0);
8616             return ;
8617         } else {
8618            Reset(TRUE, FALSE);
8619            CopyBoard(boards[0], initial_position);
8620            initialRulePlies = FENrulePlies;
8621            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8622            else gameMode = MachinePlaysBlack;
8623            DrawPosition(FALSE, boards[currentMove]);
8624         }
8625         return;
8626     }
8627
8628     /*
8629      * Look for communication commands
8630      */
8631     if (!strncmp(message, "telluser ", 9)) {
8632         if(message[9] == '\\' && message[10] == '\\')
8633             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8634         PlayTellSound();
8635         DisplayNote(message + 9);
8636         return;
8637     }
8638     if (!strncmp(message, "tellusererror ", 14)) {
8639         cps->userError = 1;
8640         if(message[14] == '\\' && message[15] == '\\')
8641             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8642         PlayTellSound();
8643         DisplayError(message + 14, 0);
8644         return;
8645     }
8646     if (!strncmp(message, "tellopponent ", 13)) {
8647       if (appData.icsActive) {
8648         if (loggedOn) {
8649           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8650           SendToICS(buf1);
8651         }
8652       } else {
8653         DisplayNote(message + 13);
8654       }
8655       return;
8656     }
8657     if (!strncmp(message, "tellothers ", 11)) {
8658       if (appData.icsActive) {
8659         if (loggedOn) {
8660           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8661           SendToICS(buf1);
8662         }
8663       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8664       return;
8665     }
8666     if (!strncmp(message, "tellall ", 8)) {
8667       if (appData.icsActive) {
8668         if (loggedOn) {
8669           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8670           SendToICS(buf1);
8671         }
8672       } else {
8673         DisplayNote(message + 8);
8674       }
8675       return;
8676     }
8677     if (strncmp(message, "warning", 7) == 0) {
8678         /* Undocumented feature, use tellusererror in new code */
8679         DisplayError(message, 0);
8680         return;
8681     }
8682     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8683         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8684         strcat(realname, " query");
8685         AskQuestion(realname, buf2, buf1, cps->pr);
8686         return;
8687     }
8688     /* Commands from the engine directly to ICS.  We don't allow these to be
8689      *  sent until we are logged on. Crafty kibitzes have been known to
8690      *  interfere with the login process.
8691      */
8692     if (loggedOn) {
8693         if (!strncmp(message, "tellics ", 8)) {
8694             SendToICS(message + 8);
8695             SendToICS("\n");
8696             return;
8697         }
8698         if (!strncmp(message, "tellicsnoalias ", 15)) {
8699             SendToICS(ics_prefix);
8700             SendToICS(message + 15);
8701             SendToICS("\n");
8702             return;
8703         }
8704         /* The following are for backward compatibility only */
8705         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8706             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8707             SendToICS(ics_prefix);
8708             SendToICS(message);
8709             SendToICS("\n");
8710             return;
8711         }
8712     }
8713     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8714         return;
8715     }
8716     if(!strncmp(message, "highlight ", 10)) {
8717         if(appData.testLegality && appData.markers) return;
8718         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8719         return;
8720     }
8721     if(!strncmp(message, "click ", 6)) {
8722         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8723         if(appData.testLegality || !appData.oneClick) return;
8724         sscanf(message+6, "%c%d%c", &f, &y, &c);
8725         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8726         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8727         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8728         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8729         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8730         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8731             LeftClick(Release, lastLeftX, lastLeftY);
8732         controlKey  = (c == ',');
8733         LeftClick(Press, x, y);
8734         LeftClick(Release, x, y);
8735         first.highlight = f;
8736         return;
8737     }
8738     /*
8739      * If the move is illegal, cancel it and redraw the board.
8740      * Also deal with other error cases.  Matching is rather loose
8741      * here to accommodate engines written before the spec.
8742      */
8743     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8744         strncmp(message, "Error", 5) == 0) {
8745         if (StrStr(message, "name") ||
8746             StrStr(message, "rating") || StrStr(message, "?") ||
8747             StrStr(message, "result") || StrStr(message, "board") ||
8748             StrStr(message, "bk") || StrStr(message, "computer") ||
8749             StrStr(message, "variant") || StrStr(message, "hint") ||
8750             StrStr(message, "random") || StrStr(message, "depth") ||
8751             StrStr(message, "accepted")) {
8752             return;
8753         }
8754         if (StrStr(message, "protover")) {
8755           /* Program is responding to input, so it's apparently done
8756              initializing, and this error message indicates it is
8757              protocol version 1.  So we don't need to wait any longer
8758              for it to initialize and send feature commands. */
8759           FeatureDone(cps, 1);
8760           cps->protocolVersion = 1;
8761           return;
8762         }
8763         cps->maybeThinking = FALSE;
8764
8765         if (StrStr(message, "draw")) {
8766             /* Program doesn't have "draw" command */
8767             cps->sendDrawOffers = 0;
8768             return;
8769         }
8770         if (cps->sendTime != 1 &&
8771             (StrStr(message, "time") || StrStr(message, "otim"))) {
8772           /* Program apparently doesn't have "time" or "otim" command */
8773           cps->sendTime = 0;
8774           return;
8775         }
8776         if (StrStr(message, "analyze")) {
8777             cps->analysisSupport = FALSE;
8778             cps->analyzing = FALSE;
8779 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8780             EditGameEvent(); // [HGM] try to preserve loaded game
8781             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8782             DisplayError(buf2, 0);
8783             return;
8784         }
8785         if (StrStr(message, "(no matching move)st")) {
8786           /* Special kludge for GNU Chess 4 only */
8787           cps->stKludge = TRUE;
8788           SendTimeControl(cps, movesPerSession, timeControl,
8789                           timeIncrement, appData.searchDepth,
8790                           searchTime);
8791           return;
8792         }
8793         if (StrStr(message, "(no matching move)sd")) {
8794           /* Special kludge for GNU Chess 4 only */
8795           cps->sdKludge = TRUE;
8796           SendTimeControl(cps, movesPerSession, timeControl,
8797                           timeIncrement, appData.searchDepth,
8798                           searchTime);
8799           return;
8800         }
8801         if (!StrStr(message, "llegal")) {
8802             return;
8803         }
8804         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8805             gameMode == IcsIdle) return;
8806         if (forwardMostMove <= backwardMostMove) return;
8807         if (pausing) PauseEvent();
8808       if(appData.forceIllegal) {
8809             // [HGM] illegal: machine refused move; force position after move into it
8810           SendToProgram("force\n", cps);
8811           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8812                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8813                 // when black is to move, while there might be nothing on a2 or black
8814                 // might already have the move. So send the board as if white has the move.
8815                 // But first we must change the stm of the engine, as it refused the last move
8816                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8817                 if(WhiteOnMove(forwardMostMove)) {
8818                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8819                     SendBoard(cps, forwardMostMove); // kludgeless board
8820                 } else {
8821                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8822                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8823                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8824                 }
8825           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8826             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8827                  gameMode == TwoMachinesPlay)
8828               SendToProgram("go\n", cps);
8829             return;
8830       } else
8831         if (gameMode == PlayFromGameFile) {
8832             /* Stop reading this game file */
8833             gameMode = EditGame;
8834             ModeHighlight();
8835         }
8836         /* [HGM] illegal-move claim should forfeit game when Xboard */
8837         /* only passes fully legal moves                            */
8838         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8839             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8840                                 "False illegal-move claim", GE_XBOARD );
8841             return; // do not take back move we tested as valid
8842         }
8843         currentMove = forwardMostMove-1;
8844         DisplayMove(currentMove-1); /* before DisplayMoveError */
8845         SwitchClocks(forwardMostMove-1); // [HGM] race
8846         DisplayBothClocks();
8847         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8848                 parseList[currentMove], _(cps->which));
8849         DisplayMoveError(buf1);
8850         DrawPosition(FALSE, boards[currentMove]);
8851
8852         SetUserThinkingEnables();
8853         return;
8854     }
8855     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8856         /* Program has a broken "time" command that
8857            outputs a string not ending in newline.
8858            Don't use it. */
8859         cps->sendTime = 0;
8860     }
8861
8862     /*
8863      * If chess program startup fails, exit with an error message.
8864      * Attempts to recover here are futile. [HGM] Well, we try anyway
8865      */
8866     if ((StrStr(message, "unknown host") != NULL)
8867         || (StrStr(message, "No remote directory") != NULL)
8868         || (StrStr(message, "not found") != NULL)
8869         || (StrStr(message, "No such file") != NULL)
8870         || (StrStr(message, "can't alloc") != NULL)
8871         || (StrStr(message, "Permission denied") != NULL)) {
8872
8873         cps->maybeThinking = FALSE;
8874         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8875                 _(cps->which), cps->program, cps->host, message);
8876         RemoveInputSource(cps->isr);
8877         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8878             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8879             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8880         }
8881         return;
8882     }
8883
8884     /*
8885      * Look for hint output
8886      */
8887     if (sscanf(message, "Hint: %s", buf1) == 1) {
8888         if (cps == &first && hintRequested) {
8889             hintRequested = FALSE;
8890             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8891                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8892                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8893                                     PosFlags(forwardMostMove),
8894                                     fromY, fromX, toY, toX, promoChar, buf1);
8895                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8896                 DisplayInformation(buf2);
8897             } else {
8898                 /* Hint move could not be parsed!? */
8899               snprintf(buf2, sizeof(buf2),
8900                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8901                         buf1, _(cps->which));
8902                 DisplayError(buf2, 0);
8903             }
8904         } else {
8905           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8906         }
8907         return;
8908     }
8909
8910     /*
8911      * Ignore other messages if game is not in progress
8912      */
8913     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8914         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8915
8916     /*
8917      * look for win, lose, draw, or draw offer
8918      */
8919     if (strncmp(message, "1-0", 3) == 0) {
8920         char *p, *q, *r = "";
8921         p = strchr(message, '{');
8922         if (p) {
8923             q = strchr(p, '}');
8924             if (q) {
8925                 *q = NULLCHAR;
8926                 r = p + 1;
8927             }
8928         }
8929         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8930         return;
8931     } else if (strncmp(message, "0-1", 3) == 0) {
8932         char *p, *q, *r = "";
8933         p = strchr(message, '{');
8934         if (p) {
8935             q = strchr(p, '}');
8936             if (q) {
8937                 *q = NULLCHAR;
8938                 r = p + 1;
8939             }
8940         }
8941         /* Kludge for Arasan 4.1 bug */
8942         if (strcmp(r, "Black resigns") == 0) {
8943             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8944             return;
8945         }
8946         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8947         return;
8948     } else if (strncmp(message, "1/2", 3) == 0) {
8949         char *p, *q, *r = "";
8950         p = strchr(message, '{');
8951         if (p) {
8952             q = strchr(p, '}');
8953             if (q) {
8954                 *q = NULLCHAR;
8955                 r = p + 1;
8956             }
8957         }
8958
8959         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8960         return;
8961
8962     } else if (strncmp(message, "White resign", 12) == 0) {
8963         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8964         return;
8965     } else if (strncmp(message, "Black resign", 12) == 0) {
8966         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8967         return;
8968     } else if (strncmp(message, "White matches", 13) == 0 ||
8969                strncmp(message, "Black matches", 13) == 0   ) {
8970         /* [HGM] ignore GNUShogi noises */
8971         return;
8972     } else if (strncmp(message, "White", 5) == 0 &&
8973                message[5] != '(' &&
8974                StrStr(message, "Black") == NULL) {
8975         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8976         return;
8977     } else if (strncmp(message, "Black", 5) == 0 &&
8978                message[5] != '(') {
8979         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8980         return;
8981     } else if (strcmp(message, "resign") == 0 ||
8982                strcmp(message, "computer resigns") == 0) {
8983         switch (gameMode) {
8984           case MachinePlaysBlack:
8985           case IcsPlayingBlack:
8986             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8987             break;
8988           case MachinePlaysWhite:
8989           case IcsPlayingWhite:
8990             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8991             break;
8992           case TwoMachinesPlay:
8993             if (cps->twoMachinesColor[0] == 'w')
8994               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8995             else
8996               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8997             break;
8998           default:
8999             /* can't happen */
9000             break;
9001         }
9002         return;
9003     } else if (strncmp(message, "opponent mates", 14) == 0) {
9004         switch (gameMode) {
9005           case MachinePlaysBlack:
9006           case IcsPlayingBlack:
9007             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9008             break;
9009           case MachinePlaysWhite:
9010           case IcsPlayingWhite:
9011             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9012             break;
9013           case TwoMachinesPlay:
9014             if (cps->twoMachinesColor[0] == 'w')
9015               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9016             else
9017               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9018             break;
9019           default:
9020             /* can't happen */
9021             break;
9022         }
9023         return;
9024     } else if (strncmp(message, "computer mates", 14) == 0) {
9025         switch (gameMode) {
9026           case MachinePlaysBlack:
9027           case IcsPlayingBlack:
9028             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9029             break;
9030           case MachinePlaysWhite:
9031           case IcsPlayingWhite:
9032             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9033             break;
9034           case TwoMachinesPlay:
9035             if (cps->twoMachinesColor[0] == 'w')
9036               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9037             else
9038               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9039             break;
9040           default:
9041             /* can't happen */
9042             break;
9043         }
9044         return;
9045     } else if (strncmp(message, "checkmate", 9) == 0) {
9046         if (WhiteOnMove(forwardMostMove)) {
9047             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9048         } else {
9049             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9050         }
9051         return;
9052     } else if (strstr(message, "Draw") != NULL ||
9053                strstr(message, "game is a draw") != NULL) {
9054         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9055         return;
9056     } else if (strstr(message, "offer") != NULL &&
9057                strstr(message, "draw") != NULL) {
9058 #if ZIPPY
9059         if (appData.zippyPlay && first.initDone) {
9060             /* Relay offer to ICS */
9061             SendToICS(ics_prefix);
9062             SendToICS("draw\n");
9063         }
9064 #endif
9065         cps->offeredDraw = 2; /* valid until this engine moves twice */
9066         if (gameMode == TwoMachinesPlay) {
9067             if (cps->other->offeredDraw) {
9068                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9069             /* [HGM] in two-machine mode we delay relaying draw offer      */
9070             /* until after we also have move, to see if it is really claim */
9071             }
9072         } else if (gameMode == MachinePlaysWhite ||
9073                    gameMode == MachinePlaysBlack) {
9074           if (userOfferedDraw) {
9075             DisplayInformation(_("Machine accepts your draw offer"));
9076             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9077           } else {
9078             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9079           }
9080         }
9081     }
9082
9083
9084     /*
9085      * Look for thinking output
9086      */
9087     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9088           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9089                                 ) {
9090         int plylev, mvleft, mvtot, curscore, time;
9091         char mvname[MOVE_LEN];
9092         u64 nodes; // [DM]
9093         char plyext;
9094         int ignore = FALSE;
9095         int prefixHint = FALSE;
9096         mvname[0] = NULLCHAR;
9097
9098         switch (gameMode) {
9099           case MachinePlaysBlack:
9100           case IcsPlayingBlack:
9101             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9102             break;
9103           case MachinePlaysWhite:
9104           case IcsPlayingWhite:
9105             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9106             break;
9107           case AnalyzeMode:
9108           case AnalyzeFile:
9109             break;
9110           case IcsObserving: /* [DM] icsEngineAnalyze */
9111             if (!appData.icsEngineAnalyze) ignore = TRUE;
9112             break;
9113           case TwoMachinesPlay:
9114             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9115                 ignore = TRUE;
9116             }
9117             break;
9118           default:
9119             ignore = TRUE;
9120             break;
9121         }
9122
9123         if (!ignore) {
9124             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9125             buf1[0] = NULLCHAR;
9126             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9127                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9128
9129                 if (plyext != ' ' && plyext != '\t') {
9130                     time *= 100;
9131                 }
9132
9133                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9134                 if( cps->scoreIsAbsolute &&
9135                     ( gameMode == MachinePlaysBlack ||
9136                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9137                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9138                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9139                      !WhiteOnMove(currentMove)
9140                     ) )
9141                 {
9142                     curscore = -curscore;
9143                 }
9144
9145                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9146
9147                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9148                         char buf[MSG_SIZ];
9149                         FILE *f;
9150                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9151                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9152                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9153                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9154                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9155                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9156                                 fclose(f);
9157                         }
9158                         else
9159                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9160                           DisplayError(_("failed writing PV"), 0);
9161                 }
9162
9163                 tempStats.depth = plylev;
9164                 tempStats.nodes = nodes;
9165                 tempStats.time = time;
9166                 tempStats.score = curscore;
9167                 tempStats.got_only_move = 0;
9168
9169                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9170                         int ticklen;
9171
9172                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9173                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9174                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9175                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9176                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9177                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9178                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9179                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9180                 }
9181
9182                 /* Buffer overflow protection */
9183                 if (pv[0] != NULLCHAR) {
9184                     if (strlen(pv) >= sizeof(tempStats.movelist)
9185                         && appData.debugMode) {
9186                         fprintf(debugFP,
9187                                 "PV is too long; using the first %u bytes.\n",
9188                                 (unsigned) sizeof(tempStats.movelist) - 1);
9189                     }
9190
9191                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9192                 } else {
9193                     sprintf(tempStats.movelist, " no PV\n");
9194                 }
9195
9196                 if (tempStats.seen_stat) {
9197                     tempStats.ok_to_send = 1;
9198                 }
9199
9200                 if (strchr(tempStats.movelist, '(') != NULL) {
9201                     tempStats.line_is_book = 1;
9202                     tempStats.nr_moves = 0;
9203                     tempStats.moves_left = 0;
9204                 } else {
9205                     tempStats.line_is_book = 0;
9206                 }
9207
9208                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9209                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9210
9211                 SendProgramStatsToFrontend( cps, &tempStats );
9212
9213                 /*
9214                     [AS] Protect the thinkOutput buffer from overflow... this
9215                     is only useful if buf1 hasn't overflowed first!
9216                 */
9217                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9218                          plylev,
9219                          (gameMode == TwoMachinesPlay ?
9220                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9221                          ((double) curscore) / 100.0,
9222                          prefixHint ? lastHint : "",
9223                          prefixHint ? " " : "" );
9224
9225                 if( buf1[0] != NULLCHAR ) {
9226                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9227
9228                     if( strlen(pv) > max_len ) {
9229                         if( appData.debugMode) {
9230                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9231                         }
9232                         pv[max_len+1] = '\0';
9233                     }
9234
9235                     strcat( thinkOutput, pv);
9236                 }
9237
9238                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9239                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9240                     DisplayMove(currentMove - 1);
9241                 }
9242                 return;
9243
9244             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9245                 /* crafty (9.25+) says "(only move) <move>"
9246                  * if there is only 1 legal move
9247                  */
9248                 sscanf(p, "(only move) %s", buf1);
9249                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9250                 sprintf(programStats.movelist, "%s (only move)", buf1);
9251                 programStats.depth = 1;
9252                 programStats.nr_moves = 1;
9253                 programStats.moves_left = 1;
9254                 programStats.nodes = 1;
9255                 programStats.time = 1;
9256                 programStats.got_only_move = 1;
9257
9258                 /* Not really, but we also use this member to
9259                    mean "line isn't going to change" (Crafty
9260                    isn't searching, so stats won't change) */
9261                 programStats.line_is_book = 1;
9262
9263                 SendProgramStatsToFrontend( cps, &programStats );
9264
9265                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9266                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9267                     DisplayMove(currentMove - 1);
9268                 }
9269                 return;
9270             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9271                               &time, &nodes, &plylev, &mvleft,
9272                               &mvtot, mvname) >= 5) {
9273                 /* The stat01: line is from Crafty (9.29+) in response
9274                    to the "." command */
9275                 programStats.seen_stat = 1;
9276                 cps->maybeThinking = TRUE;
9277
9278                 if (programStats.got_only_move || !appData.periodicUpdates)
9279                   return;
9280
9281                 programStats.depth = plylev;
9282                 programStats.time = time;
9283                 programStats.nodes = nodes;
9284                 programStats.moves_left = mvleft;
9285                 programStats.nr_moves = mvtot;
9286                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9287                 programStats.ok_to_send = 1;
9288                 programStats.movelist[0] = '\0';
9289
9290                 SendProgramStatsToFrontend( cps, &programStats );
9291
9292                 return;
9293
9294             } else if (strncmp(message,"++",2) == 0) {
9295                 /* Crafty 9.29+ outputs this */
9296                 programStats.got_fail = 2;
9297                 return;
9298
9299             } else if (strncmp(message,"--",2) == 0) {
9300                 /* Crafty 9.29+ outputs this */
9301                 programStats.got_fail = 1;
9302                 return;
9303
9304             } else if (thinkOutput[0] != NULLCHAR &&
9305                        strncmp(message, "    ", 4) == 0) {
9306                 unsigned message_len;
9307
9308                 p = message;
9309                 while (*p && *p == ' ') p++;
9310
9311                 message_len = strlen( p );
9312
9313                 /* [AS] Avoid buffer overflow */
9314                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9315                     strcat(thinkOutput, " ");
9316                     strcat(thinkOutput, p);
9317                 }
9318
9319                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9320                     strcat(programStats.movelist, " ");
9321                     strcat(programStats.movelist, p);
9322                 }
9323
9324                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9325                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9326                     DisplayMove(currentMove - 1);
9327                 }
9328                 return;
9329             }
9330         }
9331         else {
9332             buf1[0] = NULLCHAR;
9333
9334             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9335                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9336             {
9337                 ChessProgramStats cpstats;
9338
9339                 if (plyext != ' ' && plyext != '\t') {
9340                     time *= 100;
9341                 }
9342
9343                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9344                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9345                     curscore = -curscore;
9346                 }
9347
9348                 cpstats.depth = plylev;
9349                 cpstats.nodes = nodes;
9350                 cpstats.time = time;
9351                 cpstats.score = curscore;
9352                 cpstats.got_only_move = 0;
9353                 cpstats.movelist[0] = '\0';
9354
9355                 if (buf1[0] != NULLCHAR) {
9356                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9357                 }
9358
9359                 cpstats.ok_to_send = 0;
9360                 cpstats.line_is_book = 0;
9361                 cpstats.nr_moves = 0;
9362                 cpstats.moves_left = 0;
9363
9364                 SendProgramStatsToFrontend( cps, &cpstats );
9365             }
9366         }
9367     }
9368 }
9369
9370
9371 /* Parse a game score from the character string "game", and
9372    record it as the history of the current game.  The game
9373    score is NOT assumed to start from the standard position.
9374    The display is not updated in any way.
9375    */
9376 void
9377 ParseGameHistory (char *game)
9378 {
9379     ChessMove moveType;
9380     int fromX, fromY, toX, toY, boardIndex;
9381     char promoChar;
9382     char *p, *q;
9383     char buf[MSG_SIZ];
9384
9385     if (appData.debugMode)
9386       fprintf(debugFP, "Parsing game history: %s\n", game);
9387
9388     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9389     gameInfo.site = StrSave(appData.icsHost);
9390     gameInfo.date = PGNDate();
9391     gameInfo.round = StrSave("-");
9392
9393     /* Parse out names of players */
9394     while (*game == ' ') game++;
9395     p = buf;
9396     while (*game != ' ') *p++ = *game++;
9397     *p = NULLCHAR;
9398     gameInfo.white = StrSave(buf);
9399     while (*game == ' ') game++;
9400     p = buf;
9401     while (*game != ' ' && *game != '\n') *p++ = *game++;
9402     *p = NULLCHAR;
9403     gameInfo.black = StrSave(buf);
9404
9405     /* Parse moves */
9406     boardIndex = blackPlaysFirst ? 1 : 0;
9407     yynewstr(game);
9408     for (;;) {
9409         yyboardindex = boardIndex;
9410         moveType = (ChessMove) Myylex();
9411         switch (moveType) {
9412           case IllegalMove:             /* maybe suicide chess, etc. */
9413   if (appData.debugMode) {
9414     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9415     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9416     setbuf(debugFP, NULL);
9417   }
9418           case WhitePromotion:
9419           case BlackPromotion:
9420           case WhiteNonPromotion:
9421           case BlackNonPromotion:
9422           case NormalMove:
9423           case WhiteCapturesEnPassant:
9424           case BlackCapturesEnPassant:
9425           case WhiteKingSideCastle:
9426           case WhiteQueenSideCastle:
9427           case BlackKingSideCastle:
9428           case BlackQueenSideCastle:
9429           case WhiteKingSideCastleWild:
9430           case WhiteQueenSideCastleWild:
9431           case BlackKingSideCastleWild:
9432           case BlackQueenSideCastleWild:
9433           /* PUSH Fabien */
9434           case WhiteHSideCastleFR:
9435           case WhiteASideCastleFR:
9436           case BlackHSideCastleFR:
9437           case BlackASideCastleFR:
9438           /* POP Fabien */
9439             fromX = currentMoveString[0] - AAA;
9440             fromY = currentMoveString[1] - ONE;
9441             toX = currentMoveString[2] - AAA;
9442             toY = currentMoveString[3] - ONE;
9443             promoChar = currentMoveString[4];
9444             break;
9445           case WhiteDrop:
9446           case BlackDrop:
9447             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9448             fromX = moveType == WhiteDrop ?
9449               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9450             (int) CharToPiece(ToLower(currentMoveString[0]));
9451             fromY = DROP_RANK;
9452             toX = currentMoveString[2] - AAA;
9453             toY = currentMoveString[3] - ONE;
9454             promoChar = NULLCHAR;
9455             break;
9456           case AmbiguousMove:
9457             /* bug? */
9458             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9459   if (appData.debugMode) {
9460     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9461     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9462     setbuf(debugFP, NULL);
9463   }
9464             DisplayError(buf, 0);
9465             return;
9466           case ImpossibleMove:
9467             /* bug? */
9468             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9469   if (appData.debugMode) {
9470     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9471     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9472     setbuf(debugFP, NULL);
9473   }
9474             DisplayError(buf, 0);
9475             return;
9476           case EndOfFile:
9477             if (boardIndex < backwardMostMove) {
9478                 /* Oops, gap.  How did that happen? */
9479                 DisplayError(_("Gap in move list"), 0);
9480                 return;
9481             }
9482             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9483             if (boardIndex > forwardMostMove) {
9484                 forwardMostMove = boardIndex;
9485             }
9486             return;
9487           case ElapsedTime:
9488             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9489                 strcat(parseList[boardIndex-1], " ");
9490                 strcat(parseList[boardIndex-1], yy_text);
9491             }
9492             continue;
9493           case Comment:
9494           case PGNTag:
9495           case NAG:
9496           default:
9497             /* ignore */
9498             continue;
9499           case WhiteWins:
9500           case BlackWins:
9501           case GameIsDrawn:
9502           case GameUnfinished:
9503             if (gameMode == IcsExamining) {
9504                 if (boardIndex < backwardMostMove) {
9505                     /* Oops, gap.  How did that happen? */
9506                     return;
9507                 }
9508                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9509                 return;
9510             }
9511             gameInfo.result = moveType;
9512             p = strchr(yy_text, '{');
9513             if (p == NULL) p = strchr(yy_text, '(');
9514             if (p == NULL) {
9515                 p = yy_text;
9516                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9517             } else {
9518                 q = strchr(p, *p == '{' ? '}' : ')');
9519                 if (q != NULL) *q = NULLCHAR;
9520                 p++;
9521             }
9522             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9523             gameInfo.resultDetails = StrSave(p);
9524             continue;
9525         }
9526         if (boardIndex >= forwardMostMove &&
9527             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9528             backwardMostMove = blackPlaysFirst ? 1 : 0;
9529             return;
9530         }
9531         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9532                                  fromY, fromX, toY, toX, promoChar,
9533                                  parseList[boardIndex]);
9534         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9535         /* currentMoveString is set as a side-effect of yylex */
9536         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9537         strcat(moveList[boardIndex], "\n");
9538         boardIndex++;
9539         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9540         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9541           case MT_NONE:
9542           case MT_STALEMATE:
9543           default:
9544             break;
9545           case MT_CHECK:
9546             if(gameInfo.variant != VariantShogi)
9547                 strcat(parseList[boardIndex - 1], "+");
9548             break;
9549           case MT_CHECKMATE:
9550           case MT_STAINMATE:
9551             strcat(parseList[boardIndex - 1], "#");
9552             break;
9553         }
9554     }
9555 }
9556
9557
9558 /* Apply a move to the given board  */
9559 void
9560 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9561 {
9562   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9563   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9564
9565     /* [HGM] compute & store e.p. status and castling rights for new position */
9566     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9567
9568       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9569       oldEP = (signed char)board[EP_STATUS];
9570       board[EP_STATUS] = EP_NONE;
9571
9572   if (fromY == DROP_RANK) {
9573         /* must be first */
9574         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9575             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9576             return;
9577         }
9578         piece = board[toY][toX] = (ChessSquare) fromX;
9579   } else {
9580       int i;
9581
9582       if( board[toY][toX] != EmptySquare )
9583            board[EP_STATUS] = EP_CAPTURE;
9584
9585       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9586            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9587                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9588       } else
9589       if( board[fromY][fromX] == WhitePawn ) {
9590            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9591                board[EP_STATUS] = EP_PAWN_MOVE;
9592            if( toY-fromY==2) {
9593                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9594                         gameInfo.variant != VariantBerolina || toX < fromX)
9595                       board[EP_STATUS] = toX | berolina;
9596                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9597                         gameInfo.variant != VariantBerolina || toX > fromX)
9598                       board[EP_STATUS] = toX;
9599            }
9600       } else
9601       if( board[fromY][fromX] == BlackPawn ) {
9602            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9603                board[EP_STATUS] = EP_PAWN_MOVE;
9604            if( toY-fromY== -2) {
9605                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9606                         gameInfo.variant != VariantBerolina || toX < fromX)
9607                       board[EP_STATUS] = toX | berolina;
9608                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9609                         gameInfo.variant != VariantBerolina || toX > fromX)
9610                       board[EP_STATUS] = toX;
9611            }
9612        }
9613
9614        for(i=0; i<nrCastlingRights; i++) {
9615            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9616               board[CASTLING][i] == toX   && castlingRank[i] == toY
9617              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9618        }
9619
9620        if(gameInfo.variant == VariantSChess) { // update virginity
9621            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9622            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9623            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9624            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9625        }
9626
9627      if (fromX == toX && fromY == toY) return;
9628
9629      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9630      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9631      if(gameInfo.variant == VariantKnightmate)
9632          king += (int) WhiteUnicorn - (int) WhiteKing;
9633
9634     /* Code added by Tord: */
9635     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9636     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9637         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9638       board[fromY][fromX] = EmptySquare;
9639       board[toY][toX] = EmptySquare;
9640       if((toX > fromX) != (piece == WhiteRook)) {
9641         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9642       } else {
9643         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9644       }
9645     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9646                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9647       board[fromY][fromX] = EmptySquare;
9648       board[toY][toX] = EmptySquare;
9649       if((toX > fromX) != (piece == BlackRook)) {
9650         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9651       } else {
9652         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9653       }
9654     /* End of code added by Tord */
9655
9656     } else if (board[fromY][fromX] == king
9657         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9658         && toY == fromY && toX > fromX+1) {
9659         board[fromY][fromX] = EmptySquare;
9660         board[toY][toX] = king;
9661         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9662         board[fromY][BOARD_RGHT-1] = EmptySquare;
9663     } else if (board[fromY][fromX] == king
9664         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9665                && toY == fromY && toX < fromX-1) {
9666         board[fromY][fromX] = EmptySquare;
9667         board[toY][toX] = king;
9668         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9669         board[fromY][BOARD_LEFT] = EmptySquare;
9670     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9671                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9672                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9673                ) {
9674         /* white pawn promotion */
9675         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9676         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9677             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9678         board[fromY][fromX] = EmptySquare;
9679     } else if ((fromY >= BOARD_HEIGHT>>1)
9680                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9681                && (toX != fromX)
9682                && gameInfo.variant != VariantXiangqi
9683                && gameInfo.variant != VariantBerolina
9684                && (board[fromY][fromX] == WhitePawn)
9685                && (board[toY][toX] == EmptySquare)) {
9686         board[fromY][fromX] = EmptySquare;
9687         board[toY][toX] = WhitePawn;
9688         captured = board[toY - 1][toX];
9689         board[toY - 1][toX] = EmptySquare;
9690     } else if ((fromY == BOARD_HEIGHT-4)
9691                && (toX == fromX)
9692                && gameInfo.variant == VariantBerolina
9693                && (board[fromY][fromX] == WhitePawn)
9694                && (board[toY][toX] == EmptySquare)) {
9695         board[fromY][fromX] = EmptySquare;
9696         board[toY][toX] = WhitePawn;
9697         if(oldEP & EP_BEROLIN_A) {
9698                 captured = board[fromY][fromX-1];
9699                 board[fromY][fromX-1] = EmptySquare;
9700         }else{  captured = board[fromY][fromX+1];
9701                 board[fromY][fromX+1] = EmptySquare;
9702         }
9703     } else if (board[fromY][fromX] == king
9704         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9705                && toY == fromY && toX > fromX+1) {
9706         board[fromY][fromX] = EmptySquare;
9707         board[toY][toX] = king;
9708         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9709         board[fromY][BOARD_RGHT-1] = EmptySquare;
9710     } else if (board[fromY][fromX] == king
9711         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9712                && toY == fromY && toX < fromX-1) {
9713         board[fromY][fromX] = EmptySquare;
9714         board[toY][toX] = king;
9715         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9716         board[fromY][BOARD_LEFT] = EmptySquare;
9717     } else if (fromY == 7 && fromX == 3
9718                && board[fromY][fromX] == BlackKing
9719                && toY == 7 && toX == 5) {
9720         board[fromY][fromX] = EmptySquare;
9721         board[toY][toX] = BlackKing;
9722         board[fromY][7] = EmptySquare;
9723         board[toY][4] = BlackRook;
9724     } else if (fromY == 7 && fromX == 3
9725                && board[fromY][fromX] == BlackKing
9726                && toY == 7 && toX == 1) {
9727         board[fromY][fromX] = EmptySquare;
9728         board[toY][toX] = BlackKing;
9729         board[fromY][0] = EmptySquare;
9730         board[toY][2] = BlackRook;
9731     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9732                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9733                && toY < promoRank && promoChar
9734                ) {
9735         /* black pawn promotion */
9736         board[toY][toX] = CharToPiece(ToLower(promoChar));
9737         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9738             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9739         board[fromY][fromX] = EmptySquare;
9740     } else if ((fromY < BOARD_HEIGHT>>1)
9741                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9742                && (toX != fromX)
9743                && gameInfo.variant != VariantXiangqi
9744                && gameInfo.variant != VariantBerolina
9745                && (board[fromY][fromX] == BlackPawn)
9746                && (board[toY][toX] == EmptySquare)) {
9747         board[fromY][fromX] = EmptySquare;
9748         board[toY][toX] = BlackPawn;
9749         captured = board[toY + 1][toX];
9750         board[toY + 1][toX] = EmptySquare;
9751     } else if ((fromY == 3)
9752                && (toX == fromX)
9753                && gameInfo.variant == VariantBerolina
9754                && (board[fromY][fromX] == BlackPawn)
9755                && (board[toY][toX] == EmptySquare)) {
9756         board[fromY][fromX] = EmptySquare;
9757         board[toY][toX] = BlackPawn;
9758         if(oldEP & EP_BEROLIN_A) {
9759                 captured = board[fromY][fromX-1];
9760                 board[fromY][fromX-1] = EmptySquare;
9761         }else{  captured = board[fromY][fromX+1];
9762                 board[fromY][fromX+1] = EmptySquare;
9763         }
9764     } else {
9765         board[toY][toX] = board[fromY][fromX];
9766         board[fromY][fromX] = EmptySquare;
9767     }
9768   }
9769
9770     if (gameInfo.holdingsWidth != 0) {
9771
9772       /* !!A lot more code needs to be written to support holdings  */
9773       /* [HGM] OK, so I have written it. Holdings are stored in the */
9774       /* penultimate board files, so they are automaticlly stored   */
9775       /* in the game history.                                       */
9776       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9777                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9778         /* Delete from holdings, by decreasing count */
9779         /* and erasing image if necessary            */
9780         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9781         if(p < (int) BlackPawn) { /* white drop */
9782              p -= (int)WhitePawn;
9783                  p = PieceToNumber((ChessSquare)p);
9784              if(p >= gameInfo.holdingsSize) p = 0;
9785              if(--board[p][BOARD_WIDTH-2] <= 0)
9786                   board[p][BOARD_WIDTH-1] = EmptySquare;
9787              if((int)board[p][BOARD_WIDTH-2] < 0)
9788                         board[p][BOARD_WIDTH-2] = 0;
9789         } else {                  /* black drop */
9790              p -= (int)BlackPawn;
9791                  p = PieceToNumber((ChessSquare)p);
9792              if(p >= gameInfo.holdingsSize) p = 0;
9793              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9794                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9795              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9796                         board[BOARD_HEIGHT-1-p][1] = 0;
9797         }
9798       }
9799       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9800           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9801         /* [HGM] holdings: Add to holdings, if holdings exist */
9802         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9803                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9804                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9805         }
9806         p = (int) captured;
9807         if (p >= (int) BlackPawn) {
9808           p -= (int)BlackPawn;
9809           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9810                   /* in Shogi restore piece to its original  first */
9811                   captured = (ChessSquare) (DEMOTED captured);
9812                   p = DEMOTED p;
9813           }
9814           p = PieceToNumber((ChessSquare)p);
9815           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9816           board[p][BOARD_WIDTH-2]++;
9817           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9818         } else {
9819           p -= (int)WhitePawn;
9820           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9821                   captured = (ChessSquare) (DEMOTED captured);
9822                   p = DEMOTED p;
9823           }
9824           p = PieceToNumber((ChessSquare)p);
9825           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9826           board[BOARD_HEIGHT-1-p][1]++;
9827           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9828         }
9829       }
9830     } else if (gameInfo.variant == VariantAtomic) {
9831       if (captured != EmptySquare) {
9832         int y, x;
9833         for (y = toY-1; y <= toY+1; y++) {
9834           for (x = toX-1; x <= toX+1; x++) {
9835             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9836                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9837               board[y][x] = EmptySquare;
9838             }
9839           }
9840         }
9841         board[toY][toX] = EmptySquare;
9842       }
9843     }
9844     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9845         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9846     } else
9847     if(promoChar == '+') {
9848         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9849         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9850     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9851         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9852         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9853            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9854         board[toY][toX] = newPiece;
9855     }
9856     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9857                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9858         // [HGM] superchess: take promotion piece out of holdings
9859         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9860         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9861             if(!--board[k][BOARD_WIDTH-2])
9862                 board[k][BOARD_WIDTH-1] = EmptySquare;
9863         } else {
9864             if(!--board[BOARD_HEIGHT-1-k][1])
9865                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9866         }
9867     }
9868
9869 }
9870
9871 /* Updates forwardMostMove */
9872 void
9873 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9874 {
9875 //    forwardMostMove++; // [HGM] bare: moved downstream
9876
9877     (void) CoordsToAlgebraic(boards[forwardMostMove],
9878                              PosFlags(forwardMostMove),
9879                              fromY, fromX, toY, toX, promoChar,
9880                              parseList[forwardMostMove]);
9881
9882     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9883         int timeLeft; static int lastLoadFlag=0; int king, piece;
9884         piece = boards[forwardMostMove][fromY][fromX];
9885         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9886         if(gameInfo.variant == VariantKnightmate)
9887             king += (int) WhiteUnicorn - (int) WhiteKing;
9888         if(forwardMostMove == 0) {
9889             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9890                 fprintf(serverMoves, "%s;", UserName());
9891             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9892                 fprintf(serverMoves, "%s;", second.tidy);
9893             fprintf(serverMoves, "%s;", first.tidy);
9894             if(gameMode == MachinePlaysWhite)
9895                 fprintf(serverMoves, "%s;", UserName());
9896             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9897                 fprintf(serverMoves, "%s;", second.tidy);
9898         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9899         lastLoadFlag = loadFlag;
9900         // print base move
9901         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9902         // print castling suffix
9903         if( toY == fromY && piece == king ) {
9904             if(toX-fromX > 1)
9905                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9906             if(fromX-toX >1)
9907                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9908         }
9909         // e.p. suffix
9910         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9911              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9912              boards[forwardMostMove][toY][toX] == EmptySquare
9913              && fromX != toX && fromY != toY)
9914                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9915         // promotion suffix
9916         if(promoChar != NULLCHAR) {
9917             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9918                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9919                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9920             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9921         }
9922         if(!loadFlag) {
9923                 char buf[MOVE_LEN*2], *p; int len;
9924             fprintf(serverMoves, "/%d/%d",
9925                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9926             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9927             else                      timeLeft = blackTimeRemaining/1000;
9928             fprintf(serverMoves, "/%d", timeLeft);
9929                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9930                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9931                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9932                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9933             fprintf(serverMoves, "/%s", buf);
9934         }
9935         fflush(serverMoves);
9936     }
9937
9938     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9939         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9940       return;
9941     }
9942     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9943     if (commentList[forwardMostMove+1] != NULL) {
9944         free(commentList[forwardMostMove+1]);
9945         commentList[forwardMostMove+1] = NULL;
9946     }
9947     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9948     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9949     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9950     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9951     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9952     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9953     adjustedClock = FALSE;
9954     gameInfo.result = GameUnfinished;
9955     if (gameInfo.resultDetails != NULL) {
9956         free(gameInfo.resultDetails);
9957         gameInfo.resultDetails = NULL;
9958     }
9959     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9960                               moveList[forwardMostMove - 1]);
9961     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9962       case MT_NONE:
9963       case MT_STALEMATE:
9964       default:
9965         break;
9966       case MT_CHECK:
9967         if(gameInfo.variant != VariantShogi)
9968             strcat(parseList[forwardMostMove - 1], "+");
9969         break;
9970       case MT_CHECKMATE:
9971       case MT_STAINMATE:
9972         strcat(parseList[forwardMostMove - 1], "#");
9973         break;
9974     }
9975
9976 }
9977
9978 /* Updates currentMove if not pausing */
9979 void
9980 ShowMove (int fromX, int fromY, int toX, int toY)
9981 {
9982     int instant = (gameMode == PlayFromGameFile) ?
9983         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9984     if(appData.noGUI) return;
9985     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9986         if (!instant) {
9987             if (forwardMostMove == currentMove + 1) {
9988                 AnimateMove(boards[forwardMostMove - 1],
9989                             fromX, fromY, toX, toY);
9990             }
9991         }
9992         currentMove = forwardMostMove;
9993     }
9994
9995     if (instant) return;
9996
9997     DisplayMove(currentMove - 1);
9998     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9999             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10000                 SetHighlights(fromX, fromY, toX, toY);
10001             }
10002     }
10003     DrawPosition(FALSE, boards[currentMove]);
10004     DisplayBothClocks();
10005     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10006 }
10007
10008 void
10009 SendEgtPath (ChessProgramState *cps)
10010 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10011         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10012
10013         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10014
10015         while(*p) {
10016             char c, *q = name+1, *r, *s;
10017
10018             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10019             while(*p && *p != ',') *q++ = *p++;
10020             *q++ = ':'; *q = 0;
10021             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10022                 strcmp(name, ",nalimov:") == 0 ) {
10023                 // take nalimov path from the menu-changeable option first, if it is defined
10024               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10025                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10026             } else
10027             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10028                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10029                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10030                 s = r = StrStr(s, ":") + 1; // beginning of path info
10031                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10032                 c = *r; *r = 0;             // temporarily null-terminate path info
10033                     *--q = 0;               // strip of trailig ':' from name
10034                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10035                 *r = c;
10036                 SendToProgram(buf,cps);     // send egtbpath command for this format
10037             }
10038             if(*p == ',') p++; // read away comma to position for next format name
10039         }
10040 }
10041
10042 static int
10043 NonStandardBoardSize ()
10044 {
10045       /* [HGM] Awkward testing. Should really be a table */
10046       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10047       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10048       if( gameInfo.variant == VariantXiangqi )
10049            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10050       if( gameInfo.variant == VariantShogi )
10051            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10052       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10053            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10054       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10055           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10056            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10057       if( gameInfo.variant == VariantCourier )
10058            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10059       if( gameInfo.variant == VariantSuper )
10060            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10061       if( gameInfo.variant == VariantGreat )
10062            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10063       if( gameInfo.variant == VariantSChess )
10064            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10065       if( gameInfo.variant == VariantGrand )
10066            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10067       if( gameInfo.variant == VariantChu )
10068            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10069       return overruled;
10070 }
10071
10072 void
10073 InitChessProgram (ChessProgramState *cps, int setup)
10074 /* setup needed to setup FRC opening position */
10075 {
10076     char buf[MSG_SIZ], b[MSG_SIZ];
10077     if (appData.noChessProgram) return;
10078     hintRequested = FALSE;
10079     bookRequested = FALSE;
10080
10081     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10082     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10083     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10084     if(cps->memSize) { /* [HGM] memory */
10085       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10086         SendToProgram(buf, cps);
10087     }
10088     SendEgtPath(cps); /* [HGM] EGT */
10089     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10090       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10091         SendToProgram(buf, cps);
10092     }
10093
10094     SendToProgram(cps->initString, cps);
10095     if (gameInfo.variant != VariantNormal &&
10096         gameInfo.variant != VariantLoadable
10097         /* [HGM] also send variant if board size non-standard */
10098         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10099                                             ) {
10100       char *v = VariantName(gameInfo.variant);
10101       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10102         /* [HGM] in protocol 1 we have to assume all variants valid */
10103         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10104         DisplayFatalError(buf, 0, 1);
10105         return;
10106       }
10107
10108       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10109         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10110                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10111            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10112            if(StrStr(cps->variants, b) == NULL) {
10113                // specific sized variant not known, check if general sizing allowed
10114                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10115                    if(StrStr(cps->variants, "boardsize") == NULL) {
10116                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10117                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10118                        DisplayFatalError(buf, 0, 1);
10119                        return;
10120                    }
10121                    /* [HGM] here we really should compare with the maximum supported board size */
10122                }
10123            }
10124       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10125       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10126       SendToProgram(buf, cps);
10127     }
10128     currentlyInitializedVariant = gameInfo.variant;
10129
10130     /* [HGM] send opening position in FRC to first engine */
10131     if(setup) {
10132           SendToProgram("force\n", cps);
10133           SendBoard(cps, 0);
10134           /* engine is now in force mode! Set flag to wake it up after first move. */
10135           setboardSpoiledMachineBlack = 1;
10136     }
10137
10138     if (cps->sendICS) {
10139       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10140       SendToProgram(buf, cps);
10141     }
10142     cps->maybeThinking = FALSE;
10143     cps->offeredDraw = 0;
10144     if (!appData.icsActive) {
10145         SendTimeControl(cps, movesPerSession, timeControl,
10146                         timeIncrement, appData.searchDepth,
10147                         searchTime);
10148     }
10149     if (appData.showThinking
10150         // [HGM] thinking: four options require thinking output to be sent
10151         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10152                                 ) {
10153         SendToProgram("post\n", cps);
10154     }
10155     SendToProgram("hard\n", cps);
10156     if (!appData.ponderNextMove) {
10157         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10158            it without being sure what state we are in first.  "hard"
10159            is not a toggle, so that one is OK.
10160          */
10161         SendToProgram("easy\n", cps);
10162     }
10163     if (cps->usePing) {
10164       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10165       SendToProgram(buf, cps);
10166     }
10167     cps->initDone = TRUE;
10168     ClearEngineOutputPane(cps == &second);
10169 }
10170
10171
10172 void
10173 ResendOptions (ChessProgramState *cps)
10174 { // send the stored value of the options
10175   int i;
10176   char buf[MSG_SIZ];
10177   Option *opt = cps->option;
10178   for(i=0; i<cps->nrOptions; i++, opt++) {
10179       switch(opt->type) {
10180         case Spin:
10181         case Slider:
10182         case CheckBox:
10183             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10184           break;
10185         case ComboBox:
10186           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10187           break;
10188         default:
10189             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10190           break;
10191         case Button:
10192         case SaveButton:
10193           continue;
10194       }
10195       SendToProgram(buf, cps);
10196   }
10197 }
10198
10199 void
10200 StartChessProgram (ChessProgramState *cps)
10201 {
10202     char buf[MSG_SIZ];
10203     int err;
10204
10205     if (appData.noChessProgram) return;
10206     cps->initDone = FALSE;
10207
10208     if (strcmp(cps->host, "localhost") == 0) {
10209         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10210     } else if (*appData.remoteShell == NULLCHAR) {
10211         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10212     } else {
10213         if (*appData.remoteUser == NULLCHAR) {
10214           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10215                     cps->program);
10216         } else {
10217           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10218                     cps->host, appData.remoteUser, cps->program);
10219         }
10220         err = StartChildProcess(buf, "", &cps->pr);
10221     }
10222
10223     if (err != 0) {
10224       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10225         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10226         if(cps != &first) return;
10227         appData.noChessProgram = TRUE;
10228         ThawUI();
10229         SetNCPMode();
10230 //      DisplayFatalError(buf, err, 1);
10231 //      cps->pr = NoProc;
10232 //      cps->isr = NULL;
10233         return;
10234     }
10235
10236     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10237     if (cps->protocolVersion > 1) {
10238       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10239       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10240         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10241         cps->comboCnt = 0;  //                and values of combo boxes
10242       }
10243       SendToProgram(buf, cps);
10244       if(cps->reload) ResendOptions(cps);
10245     } else {
10246       SendToProgram("xboard\n", cps);
10247     }
10248 }
10249
10250 void
10251 TwoMachinesEventIfReady P((void))
10252 {
10253   static int curMess = 0;
10254   if (first.lastPing != first.lastPong) {
10255     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10256     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10257     return;
10258   }
10259   if (second.lastPing != second.lastPong) {
10260     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10261     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10262     return;
10263   }
10264   DisplayMessage("", ""); curMess = 0;
10265   TwoMachinesEvent();
10266 }
10267
10268 char *
10269 MakeName (char *template)
10270 {
10271     time_t clock;
10272     struct tm *tm;
10273     static char buf[MSG_SIZ];
10274     char *p = buf;
10275     int i;
10276
10277     clock = time((time_t *)NULL);
10278     tm = localtime(&clock);
10279
10280     while(*p++ = *template++) if(p[-1] == '%') {
10281         switch(*template++) {
10282           case 0:   *p = 0; return buf;
10283           case 'Y': i = tm->tm_year+1900; break;
10284           case 'y': i = tm->tm_year-100; break;
10285           case 'M': i = tm->tm_mon+1; break;
10286           case 'd': i = tm->tm_mday; break;
10287           case 'h': i = tm->tm_hour; break;
10288           case 'm': i = tm->tm_min; break;
10289           case 's': i = tm->tm_sec; break;
10290           default:  i = 0;
10291         }
10292         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10293     }
10294     return buf;
10295 }
10296
10297 int
10298 CountPlayers (char *p)
10299 {
10300     int n = 0;
10301     while(p = strchr(p, '\n')) p++, n++; // count participants
10302     return n;
10303 }
10304
10305 FILE *
10306 WriteTourneyFile (char *results, FILE *f)
10307 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10308     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10309     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10310         // create a file with tournament description
10311         fprintf(f, "-participants {%s}\n", appData.participants);
10312         fprintf(f, "-seedBase %d\n", appData.seedBase);
10313         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10314         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10315         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10316         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10317         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10318         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10319         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10320         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10321         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10322         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10323         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10324         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10325         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10326         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10327         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10328         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10329         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10330         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10331         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10332         fprintf(f, "-smpCores %d\n", appData.smpCores);
10333         if(searchTime > 0)
10334                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10335         else {
10336                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10337                 fprintf(f, "-tc %s\n", appData.timeControl);
10338                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10339         }
10340         fprintf(f, "-results \"%s\"\n", results);
10341     }
10342     return f;
10343 }
10344
10345 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10346
10347 void
10348 Substitute (char *participants, int expunge)
10349 {
10350     int i, changed, changes=0, nPlayers=0;
10351     char *p, *q, *r, buf[MSG_SIZ];
10352     if(participants == NULL) return;
10353     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10354     r = p = participants; q = appData.participants;
10355     while(*p && *p == *q) {
10356         if(*p == '\n') r = p+1, nPlayers++;
10357         p++; q++;
10358     }
10359     if(*p) { // difference
10360         while(*p && *p++ != '\n');
10361         while(*q && *q++ != '\n');
10362       changed = nPlayers;
10363         changes = 1 + (strcmp(p, q) != 0);
10364     }
10365     if(changes == 1) { // a single engine mnemonic was changed
10366         q = r; while(*q) nPlayers += (*q++ == '\n');
10367         p = buf; while(*r && (*p = *r++) != '\n') p++;
10368         *p = NULLCHAR;
10369         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10370         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10371         if(mnemonic[i]) { // The substitute is valid
10372             FILE *f;
10373             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10374                 flock(fileno(f), LOCK_EX);
10375                 ParseArgsFromFile(f);
10376                 fseek(f, 0, SEEK_SET);
10377                 FREE(appData.participants); appData.participants = participants;
10378                 if(expunge) { // erase results of replaced engine
10379                     int len = strlen(appData.results), w, b, dummy;
10380                     for(i=0; i<len; i++) {
10381                         Pairing(i, nPlayers, &w, &b, &dummy);
10382                         if((w == changed || b == changed) && appData.results[i] == '*') {
10383                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10384                             fclose(f);
10385                             return;
10386                         }
10387                     }
10388                     for(i=0; i<len; i++) {
10389                         Pairing(i, nPlayers, &w, &b, &dummy);
10390                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10391                     }
10392                 }
10393                 WriteTourneyFile(appData.results, f);
10394                 fclose(f); // release lock
10395                 return;
10396             }
10397         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10398     }
10399     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10400     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10401     free(participants);
10402     return;
10403 }
10404
10405 int
10406 CheckPlayers (char *participants)
10407 {
10408         int i;
10409         char buf[MSG_SIZ], *p;
10410         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10411         while(p = strchr(participants, '\n')) {
10412             *p = NULLCHAR;
10413             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10414             if(!mnemonic[i]) {
10415                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10416                 *p = '\n';
10417                 DisplayError(buf, 0);
10418                 return 1;
10419             }
10420             *p = '\n';
10421             participants = p + 1;
10422         }
10423         return 0;
10424 }
10425
10426 int
10427 CreateTourney (char *name)
10428 {
10429         FILE *f;
10430         if(matchMode && strcmp(name, appData.tourneyFile)) {
10431              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10432         }
10433         if(name[0] == NULLCHAR) {
10434             if(appData.participants[0])
10435                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10436             return 0;
10437         }
10438         f = fopen(name, "r");
10439         if(f) { // file exists
10440             ASSIGN(appData.tourneyFile, name);
10441             ParseArgsFromFile(f); // parse it
10442         } else {
10443             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10444             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10445                 DisplayError(_("Not enough participants"), 0);
10446                 return 0;
10447             }
10448             if(CheckPlayers(appData.participants)) return 0;
10449             ASSIGN(appData.tourneyFile, name);
10450             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10451             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10452         }
10453         fclose(f);
10454         appData.noChessProgram = FALSE;
10455         appData.clockMode = TRUE;
10456         SetGNUMode();
10457         return 1;
10458 }
10459
10460 int
10461 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10462 {
10463     char buf[MSG_SIZ], *p, *q;
10464     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10465     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10466     skip = !all && group[0]; // if group requested, we start in skip mode
10467     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10468         p = names; q = buf; header = 0;
10469         while(*p && *p != '\n') *q++ = *p++;
10470         *q = 0;
10471         if(*p == '\n') p++;
10472         if(buf[0] == '#') {
10473             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10474             depth++; // we must be entering a new group
10475             if(all) continue; // suppress printing group headers when complete list requested
10476             header = 1;
10477             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10478         }
10479         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10480         if(engineList[i]) free(engineList[i]);
10481         engineList[i] = strdup(buf);
10482         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10483         if(engineMnemonic[i]) free(engineMnemonic[i]);
10484         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10485             strcat(buf, " (");
10486             sscanf(q + 8, "%s", buf + strlen(buf));
10487             strcat(buf, ")");
10488         }
10489         engineMnemonic[i] = strdup(buf);
10490         i++;
10491     }
10492     engineList[i] = engineMnemonic[i] = NULL;
10493     return i;
10494 }
10495
10496 // following implemented as macro to avoid type limitations
10497 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10498
10499 void
10500 SwapEngines (int n)
10501 {   // swap settings for first engine and other engine (so far only some selected options)
10502     int h;
10503     char *p;
10504     if(n == 0) return;
10505     SWAP(directory, p)
10506     SWAP(chessProgram, p)
10507     SWAP(isUCI, h)
10508     SWAP(hasOwnBookUCI, h)
10509     SWAP(protocolVersion, h)
10510     SWAP(reuse, h)
10511     SWAP(scoreIsAbsolute, h)
10512     SWAP(timeOdds, h)
10513     SWAP(logo, p)
10514     SWAP(pgnName, p)
10515     SWAP(pvSAN, h)
10516     SWAP(engOptions, p)
10517     SWAP(engInitString, p)
10518     SWAP(computerString, p)
10519     SWAP(features, p)
10520     SWAP(fenOverride, p)
10521     SWAP(NPS, h)
10522     SWAP(accumulateTC, h)
10523     SWAP(host, p)
10524 }
10525
10526 int
10527 GetEngineLine (char *s, int n)
10528 {
10529     int i;
10530     char buf[MSG_SIZ];
10531     extern char *icsNames;
10532     if(!s || !*s) return 0;
10533     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10534     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10535     if(!mnemonic[i]) return 0;
10536     if(n == 11) return 1; // just testing if there was a match
10537     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10538     if(n == 1) SwapEngines(n);
10539     ParseArgsFromString(buf);
10540     if(n == 1) SwapEngines(n);
10541     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10542         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10543         ParseArgsFromString(buf);
10544     }
10545     return 1;
10546 }
10547
10548 int
10549 SetPlayer (int player, char *p)
10550 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10551     int i;
10552     char buf[MSG_SIZ], *engineName;
10553     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10554     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10555     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10556     if(mnemonic[i]) {
10557         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10558         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10559         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10560         ParseArgsFromString(buf);
10561     } else { // no engine with this nickname is installed!
10562         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10563         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10564         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10565         ModeHighlight();
10566         DisplayError(buf, 0);
10567         return 0;
10568     }
10569     free(engineName);
10570     return i;
10571 }
10572
10573 char *recentEngines;
10574
10575 void
10576 RecentEngineEvent (int nr)
10577 {
10578     int n;
10579 //    SwapEngines(1); // bump first to second
10580 //    ReplaceEngine(&second, 1); // and load it there
10581     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10582     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10583     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10584         ReplaceEngine(&first, 0);
10585         FloatToFront(&appData.recentEngineList, command[n]);
10586     }
10587 }
10588
10589 int
10590 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10591 {   // determine players from game number
10592     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10593
10594     if(appData.tourneyType == 0) {
10595         roundsPerCycle = (nPlayers - 1) | 1;
10596         pairingsPerRound = nPlayers / 2;
10597     } else if(appData.tourneyType > 0) {
10598         roundsPerCycle = nPlayers - appData.tourneyType;
10599         pairingsPerRound = appData.tourneyType;
10600     }
10601     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10602     gamesPerCycle = gamesPerRound * roundsPerCycle;
10603     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10604     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10605     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10606     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10607     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10608     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10609
10610     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10611     if(appData.roundSync) *syncInterval = gamesPerRound;
10612
10613     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10614
10615     if(appData.tourneyType == 0) {
10616         if(curPairing == (nPlayers-1)/2 ) {
10617             *whitePlayer = curRound;
10618             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10619         } else {
10620             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10621             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10622             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10623             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10624         }
10625     } else if(appData.tourneyType > 1) {
10626         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10627         *whitePlayer = curRound + appData.tourneyType;
10628     } else if(appData.tourneyType > 0) {
10629         *whitePlayer = curPairing;
10630         *blackPlayer = curRound + appData.tourneyType;
10631     }
10632
10633     // take care of white/black alternation per round.
10634     // For cycles and games this is already taken care of by default, derived from matchGame!
10635     return curRound & 1;
10636 }
10637
10638 int
10639 NextTourneyGame (int nr, int *swapColors)
10640 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10641     char *p, *q;
10642     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10643     FILE *tf;
10644     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10645     tf = fopen(appData.tourneyFile, "r");
10646     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10647     ParseArgsFromFile(tf); fclose(tf);
10648     InitTimeControls(); // TC might be altered from tourney file
10649
10650     nPlayers = CountPlayers(appData.participants); // count participants
10651     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10652     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10653
10654     if(syncInterval) {
10655         p = q = appData.results;
10656         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10657         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10658             DisplayMessage(_("Waiting for other game(s)"),"");
10659             waitingForGame = TRUE;
10660             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10661             return 0;
10662         }
10663         waitingForGame = FALSE;
10664     }
10665
10666     if(appData.tourneyType < 0) {
10667         if(nr>=0 && !pairingReceived) {
10668             char buf[1<<16];
10669             if(pairing.pr == NoProc) {
10670                 if(!appData.pairingEngine[0]) {
10671                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10672                     return 0;
10673                 }
10674                 StartChessProgram(&pairing); // starts the pairing engine
10675             }
10676             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10677             SendToProgram(buf, &pairing);
10678             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10679             SendToProgram(buf, &pairing);
10680             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10681         }
10682         pairingReceived = 0;                              // ... so we continue here
10683         *swapColors = 0;
10684         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10685         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10686         matchGame = 1; roundNr = nr / syncInterval + 1;
10687     }
10688
10689     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10690
10691     // redefine engines, engine dir, etc.
10692     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10693     if(first.pr == NoProc) {
10694       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10695       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10696     }
10697     if(second.pr == NoProc) {
10698       SwapEngines(1);
10699       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10700       SwapEngines(1);         // and make that valid for second engine by swapping
10701       InitEngine(&second, 1);
10702     }
10703     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10704     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10705     return OK;
10706 }
10707
10708 void
10709 NextMatchGame ()
10710 {   // performs game initialization that does not invoke engines, and then tries to start the game
10711     int res, firstWhite, swapColors = 0;
10712     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10713     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
10714         char buf[MSG_SIZ];
10715         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10716         if(strcmp(buf, currentDebugFile)) { // name has changed
10717             FILE *f = fopen(buf, "w");
10718             if(f) { // if opening the new file failed, just keep using the old one
10719                 ASSIGN(currentDebugFile, buf);
10720                 fclose(debugFP);
10721                 debugFP = f;
10722             }
10723             if(appData.serverFileName) {
10724                 if(serverFP) fclose(serverFP);
10725                 serverFP = fopen(appData.serverFileName, "w");
10726                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10727                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10728             }
10729         }
10730     }
10731     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10732     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10733     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10734     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10735     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10736     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10737     Reset(FALSE, first.pr != NoProc);
10738     res = LoadGameOrPosition(matchGame); // setup game
10739     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10740     if(!res) return; // abort when bad game/pos file
10741     TwoMachinesEvent();
10742 }
10743
10744 void
10745 UserAdjudicationEvent (int result)
10746 {
10747     ChessMove gameResult = GameIsDrawn;
10748
10749     if( result > 0 ) {
10750         gameResult = WhiteWins;
10751     }
10752     else if( result < 0 ) {
10753         gameResult = BlackWins;
10754     }
10755
10756     if( gameMode == TwoMachinesPlay ) {
10757         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10758     }
10759 }
10760
10761
10762 // [HGM] save: calculate checksum of game to make games easily identifiable
10763 int
10764 StringCheckSum (char *s)
10765 {
10766         int i = 0;
10767         if(s==NULL) return 0;
10768         while(*s) i = i*259 + *s++;
10769         return i;
10770 }
10771
10772 int
10773 GameCheckSum ()
10774 {
10775         int i, sum=0;
10776         for(i=backwardMostMove; i<forwardMostMove; i++) {
10777                 sum += pvInfoList[i].depth;
10778                 sum += StringCheckSum(parseList[i]);
10779                 sum += StringCheckSum(commentList[i]);
10780                 sum *= 261;
10781         }
10782         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10783         return sum + StringCheckSum(commentList[i]);
10784 } // end of save patch
10785
10786 void
10787 GameEnds (ChessMove result, char *resultDetails, int whosays)
10788 {
10789     GameMode nextGameMode;
10790     int isIcsGame;
10791     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10792
10793     if(endingGame) return; /* [HGM] crash: forbid recursion */
10794     endingGame = 1;
10795     if(twoBoards) { // [HGM] dual: switch back to one board
10796         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10797         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10798     }
10799     if (appData.debugMode) {
10800       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10801               result, resultDetails ? resultDetails : "(null)", whosays);
10802     }
10803
10804     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10805
10806     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10807
10808     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10809         /* If we are playing on ICS, the server decides when the
10810            game is over, but the engine can offer to draw, claim
10811            a draw, or resign.
10812          */
10813 #if ZIPPY
10814         if (appData.zippyPlay && first.initDone) {
10815             if (result == GameIsDrawn) {
10816                 /* In case draw still needs to be claimed */
10817                 SendToICS(ics_prefix);
10818                 SendToICS("draw\n");
10819             } else if (StrCaseStr(resultDetails, "resign")) {
10820                 SendToICS(ics_prefix);
10821                 SendToICS("resign\n");
10822             }
10823         }
10824 #endif
10825         endingGame = 0; /* [HGM] crash */
10826         return;
10827     }
10828
10829     /* If we're loading the game from a file, stop */
10830     if (whosays == GE_FILE) {
10831       (void) StopLoadGameTimer();
10832       gameFileFP = NULL;
10833     }
10834
10835     /* Cancel draw offers */
10836     first.offeredDraw = second.offeredDraw = 0;
10837
10838     /* If this is an ICS game, only ICS can really say it's done;
10839        if not, anyone can. */
10840     isIcsGame = (gameMode == IcsPlayingWhite ||
10841                  gameMode == IcsPlayingBlack ||
10842                  gameMode == IcsObserving    ||
10843                  gameMode == IcsExamining);
10844
10845     if (!isIcsGame || whosays == GE_ICS) {
10846         /* OK -- not an ICS game, or ICS said it was done */
10847         StopClocks();
10848         if (!isIcsGame && !appData.noChessProgram)
10849           SetUserThinkingEnables();
10850
10851         /* [HGM] if a machine claims the game end we verify this claim */
10852         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10853             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10854                 char claimer;
10855                 ChessMove trueResult = (ChessMove) -1;
10856
10857                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10858                                             first.twoMachinesColor[0] :
10859                                             second.twoMachinesColor[0] ;
10860
10861                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10862                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10863                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10864                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10865                 } else
10866                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10867                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10868                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10869                 } else
10870                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10871                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10872                 }
10873
10874                 // now verify win claims, but not in drop games, as we don't understand those yet
10875                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10876                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10877                     (result == WhiteWins && claimer == 'w' ||
10878                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10879                       if (appData.debugMode) {
10880                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10881                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10882                       }
10883                       if(result != trueResult) {
10884                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10885                               result = claimer == 'w' ? BlackWins : WhiteWins;
10886                               resultDetails = buf;
10887                       }
10888                 } else
10889                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10890                     && (forwardMostMove <= backwardMostMove ||
10891                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10892                         (claimer=='b')==(forwardMostMove&1))
10893                                                                                   ) {
10894                       /* [HGM] verify: draws that were not flagged are false claims */
10895                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10896                       result = claimer == 'w' ? BlackWins : WhiteWins;
10897                       resultDetails = buf;
10898                 }
10899                 /* (Claiming a loss is accepted no questions asked!) */
10900             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10901                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10902                 result = GameUnfinished;
10903                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10904             }
10905             /* [HGM] bare: don't allow bare King to win */
10906             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10907                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10908                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10909                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10910                && result != GameIsDrawn)
10911             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10912                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10913                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10914                         if(p >= 0 && p <= (int)WhiteKing) k++;
10915                 }
10916                 if (appData.debugMode) {
10917                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10918                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10919                 }
10920                 if(k <= 1) {
10921                         result = GameIsDrawn;
10922                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10923                         resultDetails = buf;
10924                 }
10925             }
10926         }
10927
10928
10929         if(serverMoves != NULL && !loadFlag) { char c = '=';
10930             if(result==WhiteWins) c = '+';
10931             if(result==BlackWins) c = '-';
10932             if(resultDetails != NULL)
10933                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10934         }
10935         if (resultDetails != NULL) {
10936             gameInfo.result = result;
10937             gameInfo.resultDetails = StrSave(resultDetails);
10938
10939             /* display last move only if game was not loaded from file */
10940             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10941                 DisplayMove(currentMove - 1);
10942
10943             if (forwardMostMove != 0) {
10944                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10945                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10946                                                                 ) {
10947                     if (*appData.saveGameFile != NULLCHAR) {
10948                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10949                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10950                         else
10951                         SaveGameToFile(appData.saveGameFile, TRUE);
10952                     } else if (appData.autoSaveGames) {
10953                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10954                     }
10955                     if (*appData.savePositionFile != NULLCHAR) {
10956                         SavePositionToFile(appData.savePositionFile);
10957                     }
10958                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10959                 }
10960             }
10961
10962             /* Tell program how game ended in case it is learning */
10963             /* [HGM] Moved this to after saving the PGN, just in case */
10964             /* engine died and we got here through time loss. In that */
10965             /* case we will get a fatal error writing the pipe, which */
10966             /* would otherwise lose us the PGN.                       */
10967             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10968             /* output during GameEnds should never be fatal anymore   */
10969             if (gameMode == MachinePlaysWhite ||
10970                 gameMode == MachinePlaysBlack ||
10971                 gameMode == TwoMachinesPlay ||
10972                 gameMode == IcsPlayingWhite ||
10973                 gameMode == IcsPlayingBlack ||
10974                 gameMode == BeginningOfGame) {
10975                 char buf[MSG_SIZ];
10976                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10977                         resultDetails);
10978                 if (first.pr != NoProc) {
10979                     SendToProgram(buf, &first);
10980                 }
10981                 if (second.pr != NoProc &&
10982                     gameMode == TwoMachinesPlay) {
10983                     SendToProgram(buf, &second);
10984                 }
10985             }
10986         }
10987
10988         if (appData.icsActive) {
10989             if (appData.quietPlay &&
10990                 (gameMode == IcsPlayingWhite ||
10991                  gameMode == IcsPlayingBlack)) {
10992                 SendToICS(ics_prefix);
10993                 SendToICS("set shout 1\n");
10994             }
10995             nextGameMode = IcsIdle;
10996             ics_user_moved = FALSE;
10997             /* clean up premove.  It's ugly when the game has ended and the
10998              * premove highlights are still on the board.
10999              */
11000             if (gotPremove) {
11001               gotPremove = FALSE;
11002               ClearPremoveHighlights();
11003               DrawPosition(FALSE, boards[currentMove]);
11004             }
11005             if (whosays == GE_ICS) {
11006                 switch (result) {
11007                 case WhiteWins:
11008                     if (gameMode == IcsPlayingWhite)
11009                         PlayIcsWinSound();
11010                     else if(gameMode == IcsPlayingBlack)
11011                         PlayIcsLossSound();
11012                     break;
11013                 case BlackWins:
11014                     if (gameMode == IcsPlayingBlack)
11015                         PlayIcsWinSound();
11016                     else if(gameMode == IcsPlayingWhite)
11017                         PlayIcsLossSound();
11018                     break;
11019                 case GameIsDrawn:
11020                     PlayIcsDrawSound();
11021                     break;
11022                 default:
11023                     PlayIcsUnfinishedSound();
11024                 }
11025             }
11026             if(appData.quitNext) { ExitEvent(0); return; }
11027         } else if (gameMode == EditGame ||
11028                    gameMode == PlayFromGameFile ||
11029                    gameMode == AnalyzeMode ||
11030                    gameMode == AnalyzeFile) {
11031             nextGameMode = gameMode;
11032         } else {
11033             nextGameMode = EndOfGame;
11034         }
11035         pausing = FALSE;
11036         ModeHighlight();
11037     } else {
11038         nextGameMode = gameMode;
11039     }
11040
11041     if (appData.noChessProgram) {
11042         gameMode = nextGameMode;
11043         ModeHighlight();
11044         endingGame = 0; /* [HGM] crash */
11045         return;
11046     }
11047
11048     if (first.reuse) {
11049         /* Put first chess program into idle state */
11050         if (first.pr != NoProc &&
11051             (gameMode == MachinePlaysWhite ||
11052              gameMode == MachinePlaysBlack ||
11053              gameMode == TwoMachinesPlay ||
11054              gameMode == IcsPlayingWhite ||
11055              gameMode == IcsPlayingBlack ||
11056              gameMode == BeginningOfGame)) {
11057             SendToProgram("force\n", &first);
11058             if (first.usePing) {
11059               char buf[MSG_SIZ];
11060               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11061               SendToProgram(buf, &first);
11062             }
11063         }
11064     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11065         /* Kill off first chess program */
11066         if (first.isr != NULL)
11067           RemoveInputSource(first.isr);
11068         first.isr = NULL;
11069
11070         if (first.pr != NoProc) {
11071             ExitAnalyzeMode();
11072             DoSleep( appData.delayBeforeQuit );
11073             SendToProgram("quit\n", &first);
11074             DoSleep( appData.delayAfterQuit );
11075             DestroyChildProcess(first.pr, first.useSigterm);
11076             first.reload = TRUE;
11077         }
11078         first.pr = NoProc;
11079     }
11080     if (second.reuse) {
11081         /* Put second chess program into idle state */
11082         if (second.pr != NoProc &&
11083             gameMode == TwoMachinesPlay) {
11084             SendToProgram("force\n", &second);
11085             if (second.usePing) {
11086               char buf[MSG_SIZ];
11087               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11088               SendToProgram(buf, &second);
11089             }
11090         }
11091     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11092         /* Kill off second chess program */
11093         if (second.isr != NULL)
11094           RemoveInputSource(second.isr);
11095         second.isr = NULL;
11096
11097         if (second.pr != NoProc) {
11098             DoSleep( appData.delayBeforeQuit );
11099             SendToProgram("quit\n", &second);
11100             DoSleep( appData.delayAfterQuit );
11101             DestroyChildProcess(second.pr, second.useSigterm);
11102             second.reload = TRUE;
11103         }
11104         second.pr = NoProc;
11105     }
11106
11107     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11108         char resChar = '=';
11109         switch (result) {
11110         case WhiteWins:
11111           resChar = '+';
11112           if (first.twoMachinesColor[0] == 'w') {
11113             first.matchWins++;
11114           } else {
11115             second.matchWins++;
11116           }
11117           break;
11118         case BlackWins:
11119           resChar = '-';
11120           if (first.twoMachinesColor[0] == 'b') {
11121             first.matchWins++;
11122           } else {
11123             second.matchWins++;
11124           }
11125           break;
11126         case GameUnfinished:
11127           resChar = ' ';
11128         default:
11129           break;
11130         }
11131
11132         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11133         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11134             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11135             ReserveGame(nextGame, resChar); // sets nextGame
11136             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11137             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11138         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11139
11140         if (nextGame <= appData.matchGames && !abortMatch) {
11141             gameMode = nextGameMode;
11142             matchGame = nextGame; // this will be overruled in tourney mode!
11143             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11144             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11145             endingGame = 0; /* [HGM] crash */
11146             return;
11147         } else {
11148             gameMode = nextGameMode;
11149             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11150                      first.tidy, second.tidy,
11151                      first.matchWins, second.matchWins,
11152                      appData.matchGames - (first.matchWins + second.matchWins));
11153             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11154             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11155             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11156             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11157                 first.twoMachinesColor = "black\n";
11158                 second.twoMachinesColor = "white\n";
11159             } else {
11160                 first.twoMachinesColor = "white\n";
11161                 second.twoMachinesColor = "black\n";
11162             }
11163         }
11164     }
11165     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11166         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11167       ExitAnalyzeMode();
11168     gameMode = nextGameMode;
11169     ModeHighlight();
11170     endingGame = 0;  /* [HGM] crash */
11171     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11172         if(matchMode == TRUE) { // match through command line: exit with or without popup
11173             if(ranking) {
11174                 ToNrEvent(forwardMostMove);
11175                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11176                 else ExitEvent(0);
11177             } else DisplayFatalError(buf, 0, 0);
11178         } else { // match through menu; just stop, with or without popup
11179             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11180             ModeHighlight();
11181             if(ranking){
11182                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11183             } else DisplayNote(buf);
11184       }
11185       if(ranking) free(ranking);
11186     }
11187 }
11188
11189 /* Assumes program was just initialized (initString sent).
11190    Leaves program in force mode. */
11191 void
11192 FeedMovesToProgram (ChessProgramState *cps, int upto)
11193 {
11194     int i;
11195
11196     if (appData.debugMode)
11197       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11198               startedFromSetupPosition ? "position and " : "",
11199               backwardMostMove, upto, cps->which);
11200     if(currentlyInitializedVariant != gameInfo.variant) {
11201       char buf[MSG_SIZ];
11202         // [HGM] variantswitch: make engine aware of new variant
11203         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11204                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11205         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11206         SendToProgram(buf, cps);
11207         currentlyInitializedVariant = gameInfo.variant;
11208     }
11209     SendToProgram("force\n", cps);
11210     if (startedFromSetupPosition) {
11211         SendBoard(cps, backwardMostMove);
11212     if (appData.debugMode) {
11213         fprintf(debugFP, "feedMoves\n");
11214     }
11215     }
11216     for (i = backwardMostMove; i < upto; i++) {
11217         SendMoveToProgram(i, cps);
11218     }
11219 }
11220
11221
11222 int
11223 ResurrectChessProgram ()
11224 {
11225      /* The chess program may have exited.
11226         If so, restart it and feed it all the moves made so far. */
11227     static int doInit = 0;
11228
11229     if (appData.noChessProgram) return 1;
11230
11231     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11232         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11233         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11234         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11235     } else {
11236         if (first.pr != NoProc) return 1;
11237         StartChessProgram(&first);
11238     }
11239     InitChessProgram(&first, FALSE);
11240     FeedMovesToProgram(&first, currentMove);
11241
11242     if (!first.sendTime) {
11243         /* can't tell gnuchess what its clock should read,
11244            so we bow to its notion. */
11245         ResetClocks();
11246         timeRemaining[0][currentMove] = whiteTimeRemaining;
11247         timeRemaining[1][currentMove] = blackTimeRemaining;
11248     }
11249
11250     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11251                 appData.icsEngineAnalyze) && first.analysisSupport) {
11252       SendToProgram("analyze\n", &first);
11253       first.analyzing = TRUE;
11254     }
11255     return 1;
11256 }
11257
11258 /*
11259  * Button procedures
11260  */
11261 void
11262 Reset (int redraw, int init)
11263 {
11264     int i;
11265
11266     if (appData.debugMode) {
11267         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11268                 redraw, init, gameMode);
11269     }
11270     CleanupTail(); // [HGM] vari: delete any stored variations
11271     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11272     pausing = pauseExamInvalid = FALSE;
11273     startedFromSetupPosition = blackPlaysFirst = FALSE;
11274     firstMove = TRUE;
11275     whiteFlag = blackFlag = FALSE;
11276     userOfferedDraw = FALSE;
11277     hintRequested = bookRequested = FALSE;
11278     first.maybeThinking = FALSE;
11279     second.maybeThinking = FALSE;
11280     first.bookSuspend = FALSE; // [HGM] book
11281     second.bookSuspend = FALSE;
11282     thinkOutput[0] = NULLCHAR;
11283     lastHint[0] = NULLCHAR;
11284     ClearGameInfo(&gameInfo);
11285     gameInfo.variant = StringToVariant(appData.variant);
11286     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11287     ics_user_moved = ics_clock_paused = FALSE;
11288     ics_getting_history = H_FALSE;
11289     ics_gamenum = -1;
11290     white_holding[0] = black_holding[0] = NULLCHAR;
11291     ClearProgramStats();
11292     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11293
11294     ResetFrontEnd();
11295     ClearHighlights();
11296     flipView = appData.flipView;
11297     ClearPremoveHighlights();
11298     gotPremove = FALSE;
11299     alarmSounded = FALSE;
11300
11301     GameEnds(EndOfFile, NULL, GE_PLAYER);
11302     if(appData.serverMovesName != NULL) {
11303         /* [HGM] prepare to make moves file for broadcasting */
11304         clock_t t = clock();
11305         if(serverMoves != NULL) fclose(serverMoves);
11306         serverMoves = fopen(appData.serverMovesName, "r");
11307         if(serverMoves != NULL) {
11308             fclose(serverMoves);
11309             /* delay 15 sec before overwriting, so all clients can see end */
11310             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11311         }
11312         serverMoves = fopen(appData.serverMovesName, "w");
11313     }
11314
11315     ExitAnalyzeMode();
11316     gameMode = BeginningOfGame;
11317     ModeHighlight();
11318     if(appData.icsActive) gameInfo.variant = VariantNormal;
11319     currentMove = forwardMostMove = backwardMostMove = 0;
11320     MarkTargetSquares(1);
11321     InitPosition(redraw);
11322     for (i = 0; i < MAX_MOVES; i++) {
11323         if (commentList[i] != NULL) {
11324             free(commentList[i]);
11325             commentList[i] = NULL;
11326         }
11327     }
11328     ResetClocks();
11329     timeRemaining[0][0] = whiteTimeRemaining;
11330     timeRemaining[1][0] = blackTimeRemaining;
11331
11332     if (first.pr == NoProc) {
11333         StartChessProgram(&first);
11334     }
11335     if (init) {
11336             InitChessProgram(&first, startedFromSetupPosition);
11337     }
11338     DisplayTitle("");
11339     DisplayMessage("", "");
11340     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11341     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11342     ClearMap();        // [HGM] exclude: invalidate map
11343 }
11344
11345 void
11346 AutoPlayGameLoop ()
11347 {
11348     for (;;) {
11349         if (!AutoPlayOneMove())
11350           return;
11351         if (matchMode || appData.timeDelay == 0)
11352           continue;
11353         if (appData.timeDelay < 0)
11354           return;
11355         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11356         break;
11357     }
11358 }
11359
11360 void
11361 AnalyzeNextGame()
11362 {
11363     ReloadGame(1); // next game
11364 }
11365
11366 int
11367 AutoPlayOneMove ()
11368 {
11369     int fromX, fromY, toX, toY;
11370
11371     if (appData.debugMode) {
11372       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11373     }
11374
11375     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11376       return FALSE;
11377
11378     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11379       pvInfoList[currentMove].depth = programStats.depth;
11380       pvInfoList[currentMove].score = programStats.score;
11381       pvInfoList[currentMove].time  = 0;
11382       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11383       else { // append analysis of final position as comment
11384         char buf[MSG_SIZ];
11385         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11386         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11387       }
11388       programStats.depth = 0;
11389     }
11390
11391     if (currentMove >= forwardMostMove) {
11392       if(gameMode == AnalyzeFile) {
11393           if(appData.loadGameIndex == -1) {
11394             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11395           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11396           } else {
11397           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11398         }
11399       }
11400 //      gameMode = EndOfGame;
11401 //      ModeHighlight();
11402
11403       /* [AS] Clear current move marker at the end of a game */
11404       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11405
11406       return FALSE;
11407     }
11408
11409     toX = moveList[currentMove][2] - AAA;
11410     toY = moveList[currentMove][3] - ONE;
11411
11412     if (moveList[currentMove][1] == '@') {
11413         if (appData.highlightLastMove) {
11414             SetHighlights(-1, -1, toX, toY);
11415         }
11416     } else {
11417         fromX = moveList[currentMove][0] - AAA;
11418         fromY = moveList[currentMove][1] - ONE;
11419
11420         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11421
11422         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11423
11424         if (appData.highlightLastMove) {
11425             SetHighlights(fromX, fromY, toX, toY);
11426         }
11427     }
11428     DisplayMove(currentMove);
11429     SendMoveToProgram(currentMove++, &first);
11430     DisplayBothClocks();
11431     DrawPosition(FALSE, boards[currentMove]);
11432     // [HGM] PV info: always display, routine tests if empty
11433     DisplayComment(currentMove - 1, commentList[currentMove]);
11434     return TRUE;
11435 }
11436
11437
11438 int
11439 LoadGameOneMove (ChessMove readAhead)
11440 {
11441     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11442     char promoChar = NULLCHAR;
11443     ChessMove moveType;
11444     char move[MSG_SIZ];
11445     char *p, *q;
11446
11447     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11448         gameMode != AnalyzeMode && gameMode != Training) {
11449         gameFileFP = NULL;
11450         return FALSE;
11451     }
11452
11453     yyboardindex = forwardMostMove;
11454     if (readAhead != EndOfFile) {
11455       moveType = readAhead;
11456     } else {
11457       if (gameFileFP == NULL)
11458           return FALSE;
11459       moveType = (ChessMove) Myylex();
11460     }
11461
11462     done = FALSE;
11463     switch (moveType) {
11464       case Comment:
11465         if (appData.debugMode)
11466           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11467         p = yy_text;
11468
11469         /* append the comment but don't display it */
11470         AppendComment(currentMove, p, FALSE);
11471         return TRUE;
11472
11473       case WhiteCapturesEnPassant:
11474       case BlackCapturesEnPassant:
11475       case WhitePromotion:
11476       case BlackPromotion:
11477       case WhiteNonPromotion:
11478       case BlackNonPromotion:
11479       case NormalMove:
11480       case WhiteKingSideCastle:
11481       case WhiteQueenSideCastle:
11482       case BlackKingSideCastle:
11483       case BlackQueenSideCastle:
11484       case WhiteKingSideCastleWild:
11485       case WhiteQueenSideCastleWild:
11486       case BlackKingSideCastleWild:
11487       case BlackQueenSideCastleWild:
11488       /* PUSH Fabien */
11489       case WhiteHSideCastleFR:
11490       case WhiteASideCastleFR:
11491       case BlackHSideCastleFR:
11492       case BlackASideCastleFR:
11493       /* POP Fabien */
11494         if (appData.debugMode)
11495           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11496         fromX = currentMoveString[0] - AAA;
11497         fromY = currentMoveString[1] - ONE;
11498         toX = currentMoveString[2] - AAA;
11499         toY = currentMoveString[3] - ONE;
11500         promoChar = currentMoveString[4];
11501         break;
11502
11503       case WhiteDrop:
11504       case BlackDrop:
11505         if (appData.debugMode)
11506           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11507         fromX = moveType == WhiteDrop ?
11508           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11509         (int) CharToPiece(ToLower(currentMoveString[0]));
11510         fromY = DROP_RANK;
11511         toX = currentMoveString[2] - AAA;
11512         toY = currentMoveString[3] - ONE;
11513         break;
11514
11515       case WhiteWins:
11516       case BlackWins:
11517       case GameIsDrawn:
11518       case GameUnfinished:
11519         if (appData.debugMode)
11520           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11521         p = strchr(yy_text, '{');
11522         if (p == NULL) p = strchr(yy_text, '(');
11523         if (p == NULL) {
11524             p = yy_text;
11525             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11526         } else {
11527             q = strchr(p, *p == '{' ? '}' : ')');
11528             if (q != NULL) *q = NULLCHAR;
11529             p++;
11530         }
11531         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11532         GameEnds(moveType, p, GE_FILE);
11533         done = TRUE;
11534         if (cmailMsgLoaded) {
11535             ClearHighlights();
11536             flipView = WhiteOnMove(currentMove);
11537             if (moveType == GameUnfinished) flipView = !flipView;
11538             if (appData.debugMode)
11539               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11540         }
11541         break;
11542
11543       case EndOfFile:
11544         if (appData.debugMode)
11545           fprintf(debugFP, "Parser hit end of file\n");
11546         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11547           case MT_NONE:
11548           case MT_CHECK:
11549             break;
11550           case MT_CHECKMATE:
11551           case MT_STAINMATE:
11552             if (WhiteOnMove(currentMove)) {
11553                 GameEnds(BlackWins, "Black mates", GE_FILE);
11554             } else {
11555                 GameEnds(WhiteWins, "White mates", GE_FILE);
11556             }
11557             break;
11558           case MT_STALEMATE:
11559             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11560             break;
11561         }
11562         done = TRUE;
11563         break;
11564
11565       case MoveNumberOne:
11566         if (lastLoadGameStart == GNUChessGame) {
11567             /* GNUChessGames have numbers, but they aren't move numbers */
11568             if (appData.debugMode)
11569               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11570                       yy_text, (int) moveType);
11571             return LoadGameOneMove(EndOfFile); /* tail recursion */
11572         }
11573         /* else fall thru */
11574
11575       case XBoardGame:
11576       case GNUChessGame:
11577       case PGNTag:
11578         /* Reached start of next game in file */
11579         if (appData.debugMode)
11580           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11581         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11582           case MT_NONE:
11583           case MT_CHECK:
11584             break;
11585           case MT_CHECKMATE:
11586           case MT_STAINMATE:
11587             if (WhiteOnMove(currentMove)) {
11588                 GameEnds(BlackWins, "Black mates", GE_FILE);
11589             } else {
11590                 GameEnds(WhiteWins, "White mates", GE_FILE);
11591             }
11592             break;
11593           case MT_STALEMATE:
11594             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11595             break;
11596         }
11597         done = TRUE;
11598         break;
11599
11600       case PositionDiagram:     /* should not happen; ignore */
11601       case ElapsedTime:         /* ignore */
11602       case NAG:                 /* ignore */
11603         if (appData.debugMode)
11604           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11605                   yy_text, (int) moveType);
11606         return LoadGameOneMove(EndOfFile); /* tail recursion */
11607
11608       case IllegalMove:
11609         if (appData.testLegality) {
11610             if (appData.debugMode)
11611               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11612             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11613                     (forwardMostMove / 2) + 1,
11614                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11615             DisplayError(move, 0);
11616             done = TRUE;
11617         } else {
11618             if (appData.debugMode)
11619               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11620                       yy_text, currentMoveString);
11621             fromX = currentMoveString[0] - AAA;
11622             fromY = currentMoveString[1] - ONE;
11623             toX = currentMoveString[2] - AAA;
11624             toY = currentMoveString[3] - ONE;
11625             promoChar = currentMoveString[4];
11626         }
11627         break;
11628
11629       case AmbiguousMove:
11630         if (appData.debugMode)
11631           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11632         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11633                 (forwardMostMove / 2) + 1,
11634                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11635         DisplayError(move, 0);
11636         done = TRUE;
11637         break;
11638
11639       default:
11640       case ImpossibleMove:
11641         if (appData.debugMode)
11642           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11643         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11644                 (forwardMostMove / 2) + 1,
11645                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11646         DisplayError(move, 0);
11647         done = TRUE;
11648         break;
11649     }
11650
11651     if (done) {
11652         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11653             DrawPosition(FALSE, boards[currentMove]);
11654             DisplayBothClocks();
11655             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11656               DisplayComment(currentMove - 1, commentList[currentMove]);
11657         }
11658         (void) StopLoadGameTimer();
11659         gameFileFP = NULL;
11660         cmailOldMove = forwardMostMove;
11661         return FALSE;
11662     } else {
11663         /* currentMoveString is set as a side-effect of yylex */
11664
11665         thinkOutput[0] = NULLCHAR;
11666         MakeMove(fromX, fromY, toX, toY, promoChar);
11667         currentMove = forwardMostMove;
11668         return TRUE;
11669     }
11670 }
11671
11672 /* Load the nth game from the given file */
11673 int
11674 LoadGameFromFile (char *filename, int n, char *title, int useList)
11675 {
11676     FILE *f;
11677     char buf[MSG_SIZ];
11678
11679     if (strcmp(filename, "-") == 0) {
11680         f = stdin;
11681         title = "stdin";
11682     } else {
11683         f = fopen(filename, "rb");
11684         if (f == NULL) {
11685           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11686             DisplayError(buf, errno);
11687             return FALSE;
11688         }
11689     }
11690     if (fseek(f, 0, 0) == -1) {
11691         /* f is not seekable; probably a pipe */
11692         useList = FALSE;
11693     }
11694     if (useList && n == 0) {
11695         int error = GameListBuild(f);
11696         if (error) {
11697             DisplayError(_("Cannot build game list"), error);
11698         } else if (!ListEmpty(&gameList) &&
11699                    ((ListGame *) gameList.tailPred)->number > 1) {
11700             GameListPopUp(f, title);
11701             return TRUE;
11702         }
11703         GameListDestroy();
11704         n = 1;
11705     }
11706     if (n == 0) n = 1;
11707     return LoadGame(f, n, title, FALSE);
11708 }
11709
11710
11711 void
11712 MakeRegisteredMove ()
11713 {
11714     int fromX, fromY, toX, toY;
11715     char promoChar;
11716     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11717         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11718           case CMAIL_MOVE:
11719           case CMAIL_DRAW:
11720             if (appData.debugMode)
11721               fprintf(debugFP, "Restoring %s for game %d\n",
11722                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11723
11724             thinkOutput[0] = NULLCHAR;
11725             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11726             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11727             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11728             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11729             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11730             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11731             MakeMove(fromX, fromY, toX, toY, promoChar);
11732             ShowMove(fromX, fromY, toX, toY);
11733
11734             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11735               case MT_NONE:
11736               case MT_CHECK:
11737                 break;
11738
11739               case MT_CHECKMATE:
11740               case MT_STAINMATE:
11741                 if (WhiteOnMove(currentMove)) {
11742                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11743                 } else {
11744                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11745                 }
11746                 break;
11747
11748               case MT_STALEMATE:
11749                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11750                 break;
11751             }
11752
11753             break;
11754
11755           case CMAIL_RESIGN:
11756             if (WhiteOnMove(currentMove)) {
11757                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11758             } else {
11759                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11760             }
11761             break;
11762
11763           case CMAIL_ACCEPT:
11764             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11765             break;
11766
11767           default:
11768             break;
11769         }
11770     }
11771
11772     return;
11773 }
11774
11775 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11776 int
11777 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11778 {
11779     int retVal;
11780
11781     if (gameNumber > nCmailGames) {
11782         DisplayError(_("No more games in this message"), 0);
11783         return FALSE;
11784     }
11785     if (f == lastLoadGameFP) {
11786         int offset = gameNumber - lastLoadGameNumber;
11787         if (offset == 0) {
11788             cmailMsg[0] = NULLCHAR;
11789             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11790                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11791                 nCmailMovesRegistered--;
11792             }
11793             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11794             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11795                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11796             }
11797         } else {
11798             if (! RegisterMove()) return FALSE;
11799         }
11800     }
11801
11802     retVal = LoadGame(f, gameNumber, title, useList);
11803
11804     /* Make move registered during previous look at this game, if any */
11805     MakeRegisteredMove();
11806
11807     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11808         commentList[currentMove]
11809           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11810         DisplayComment(currentMove - 1, commentList[currentMove]);
11811     }
11812
11813     return retVal;
11814 }
11815
11816 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11817 int
11818 ReloadGame (int offset)
11819 {
11820     int gameNumber = lastLoadGameNumber + offset;
11821     if (lastLoadGameFP == NULL) {
11822         DisplayError(_("No game has been loaded yet"), 0);
11823         return FALSE;
11824     }
11825     if (gameNumber <= 0) {
11826         DisplayError(_("Can't back up any further"), 0);
11827         return FALSE;
11828     }
11829     if (cmailMsgLoaded) {
11830         return CmailLoadGame(lastLoadGameFP, gameNumber,
11831                              lastLoadGameTitle, lastLoadGameUseList);
11832     } else {
11833         return LoadGame(lastLoadGameFP, gameNumber,
11834                         lastLoadGameTitle, lastLoadGameUseList);
11835     }
11836 }
11837
11838 int keys[EmptySquare+1];
11839
11840 int
11841 PositionMatches (Board b1, Board b2)
11842 {
11843     int r, f, sum=0;
11844     switch(appData.searchMode) {
11845         case 1: return CompareWithRights(b1, b2);
11846         case 2:
11847             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11848                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11849             }
11850             return TRUE;
11851         case 3:
11852             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11853               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11854                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11855             }
11856             return sum==0;
11857         case 4:
11858             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11859                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11860             }
11861             return sum==0;
11862     }
11863     return TRUE;
11864 }
11865
11866 #define Q_PROMO  4
11867 #define Q_EP     3
11868 #define Q_BCASTL 2
11869 #define Q_WCASTL 1
11870
11871 int pieceList[256], quickBoard[256];
11872 ChessSquare pieceType[256] = { EmptySquare };
11873 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11874 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11875 int soughtTotal, turn;
11876 Boolean epOK, flipSearch;
11877
11878 typedef struct {
11879     unsigned char piece, to;
11880 } Move;
11881
11882 #define DSIZE (250000)
11883
11884 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11885 Move *moveDatabase = initialSpace;
11886 unsigned int movePtr, dataSize = DSIZE;
11887
11888 int
11889 MakePieceList (Board board, int *counts)
11890 {
11891     int r, f, n=Q_PROMO, total=0;
11892     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11893     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11894         int sq = f + (r<<4);
11895         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11896             quickBoard[sq] = ++n;
11897             pieceList[n] = sq;
11898             pieceType[n] = board[r][f];
11899             counts[board[r][f]]++;
11900             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11901             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11902             total++;
11903         }
11904     }
11905     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11906     return total;
11907 }
11908
11909 void
11910 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11911 {
11912     int sq = fromX + (fromY<<4);
11913     int piece = quickBoard[sq];
11914     quickBoard[sq] = 0;
11915     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11916     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11917         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11918         moveDatabase[movePtr++].piece = Q_WCASTL;
11919         quickBoard[sq] = piece;
11920         piece = quickBoard[from]; quickBoard[from] = 0;
11921         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11922     } else
11923     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11924         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11925         moveDatabase[movePtr++].piece = Q_BCASTL;
11926         quickBoard[sq] = piece;
11927         piece = quickBoard[from]; quickBoard[from] = 0;
11928         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11929     } else
11930     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11931         quickBoard[(fromY<<4)+toX] = 0;
11932         moveDatabase[movePtr].piece = Q_EP;
11933         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11934         moveDatabase[movePtr].to = sq;
11935     } else
11936     if(promoPiece != pieceType[piece]) {
11937         moveDatabase[movePtr++].piece = Q_PROMO;
11938         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11939     }
11940     moveDatabase[movePtr].piece = piece;
11941     quickBoard[sq] = piece;
11942     movePtr++;
11943 }
11944
11945 int
11946 PackGame (Board board)
11947 {
11948     Move *newSpace = NULL;
11949     moveDatabase[movePtr].piece = 0; // terminate previous game
11950     if(movePtr > dataSize) {
11951         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11952         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11953         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11954         if(newSpace) {
11955             int i;
11956             Move *p = moveDatabase, *q = newSpace;
11957             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11958             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11959             moveDatabase = newSpace;
11960         } else { // calloc failed, we must be out of memory. Too bad...
11961             dataSize = 0; // prevent calloc events for all subsequent games
11962             return 0;     // and signal this one isn't cached
11963         }
11964     }
11965     movePtr++;
11966     MakePieceList(board, counts);
11967     return movePtr;
11968 }
11969
11970 int
11971 QuickCompare (Board board, int *minCounts, int *maxCounts)
11972 {   // compare according to search mode
11973     int r, f;
11974     switch(appData.searchMode)
11975     {
11976       case 1: // exact position match
11977         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11978         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11979             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11980         }
11981         break;
11982       case 2: // can have extra material on empty squares
11983         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11984             if(board[r][f] == EmptySquare) continue;
11985             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11986         }
11987         break;
11988       case 3: // material with exact Pawn structure
11989         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11990             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11991             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11992         } // fall through to material comparison
11993       case 4: // exact material
11994         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11995         break;
11996       case 6: // material range with given imbalance
11997         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11998         // fall through to range comparison
11999       case 5: // material range
12000         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12001     }
12002     return TRUE;
12003 }
12004
12005 int
12006 QuickScan (Board board, Move *move)
12007 {   // reconstruct game,and compare all positions in it
12008     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12009     do {
12010         int piece = move->piece;
12011         int to = move->to, from = pieceList[piece];
12012         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12013           if(!piece) return -1;
12014           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12015             piece = (++move)->piece;
12016             from = pieceList[piece];
12017             counts[pieceType[piece]]--;
12018             pieceType[piece] = (ChessSquare) move->to;
12019             counts[move->to]++;
12020           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12021             counts[pieceType[quickBoard[to]]]--;
12022             quickBoard[to] = 0; total--;
12023             move++;
12024             continue;
12025           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12026             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12027             from  = pieceList[piece]; // so this must be King
12028             quickBoard[from] = 0;
12029             pieceList[piece] = to;
12030             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12031             quickBoard[from] = 0; // rook
12032             quickBoard[to] = piece;
12033             to = move->to; piece = move->piece;
12034             goto aftercastle;
12035           }
12036         }
12037         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12038         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12039         quickBoard[from] = 0;
12040       aftercastle:
12041         quickBoard[to] = piece;
12042         pieceList[piece] = to;
12043         cnt++; turn ^= 3;
12044         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12045            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12046            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12047                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12048           ) {
12049             static int lastCounts[EmptySquare+1];
12050             int i;
12051             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12052             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12053         } else stretch = 0;
12054         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12055         move++;
12056     } while(1);
12057 }
12058
12059 void
12060 InitSearch ()
12061 {
12062     int r, f;
12063     flipSearch = FALSE;
12064     CopyBoard(soughtBoard, boards[currentMove]);
12065     soughtTotal = MakePieceList(soughtBoard, maxSought);
12066     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12067     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12068     CopyBoard(reverseBoard, boards[currentMove]);
12069     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12070         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12071         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12072         reverseBoard[r][f] = piece;
12073     }
12074     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12075     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12076     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12077                  || (boards[currentMove][CASTLING][2] == NoRights ||
12078                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12079                  && (boards[currentMove][CASTLING][5] == NoRights ||
12080                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12081       ) {
12082         flipSearch = TRUE;
12083         CopyBoard(flipBoard, soughtBoard);
12084         CopyBoard(rotateBoard, reverseBoard);
12085         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12086             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12087             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12088         }
12089     }
12090     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12091     if(appData.searchMode >= 5) {
12092         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12093         MakePieceList(soughtBoard, minSought);
12094         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12095     }
12096     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12097         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12098 }
12099
12100 GameInfo dummyInfo;
12101 static int creatingBook;
12102
12103 int
12104 GameContainsPosition (FILE *f, ListGame *lg)
12105 {
12106     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12107     int fromX, fromY, toX, toY;
12108     char promoChar;
12109     static int initDone=FALSE;
12110
12111     // weed out games based on numerical tag comparison
12112     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12113     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12114     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12115     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12116     if(!initDone) {
12117         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12118         initDone = TRUE;
12119     }
12120     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12121     else CopyBoard(boards[scratch], initialPosition); // default start position
12122     if(lg->moves) {
12123         turn = btm + 1;
12124         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12125         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12126     }
12127     if(btm) plyNr++;
12128     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12129     fseek(f, lg->offset, 0);
12130     yynewfile(f);
12131     while(1) {
12132         yyboardindex = scratch;
12133         quickFlag = plyNr+1;
12134         next = Myylex();
12135         quickFlag = 0;
12136         switch(next) {
12137             case PGNTag:
12138                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12139             default:
12140                 continue;
12141
12142             case XBoardGame:
12143             case GNUChessGame:
12144                 if(plyNr) return -1; // after we have seen moves, this is for new game
12145               continue;
12146
12147             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12148             case ImpossibleMove:
12149             case WhiteWins: // game ends here with these four
12150             case BlackWins:
12151             case GameIsDrawn:
12152             case GameUnfinished:
12153                 return -1;
12154
12155             case IllegalMove:
12156                 if(appData.testLegality) return -1;
12157             case WhiteCapturesEnPassant:
12158             case BlackCapturesEnPassant:
12159             case WhitePromotion:
12160             case BlackPromotion:
12161             case WhiteNonPromotion:
12162             case BlackNonPromotion:
12163             case NormalMove:
12164             case WhiteKingSideCastle:
12165             case WhiteQueenSideCastle:
12166             case BlackKingSideCastle:
12167             case BlackQueenSideCastle:
12168             case WhiteKingSideCastleWild:
12169             case WhiteQueenSideCastleWild:
12170             case BlackKingSideCastleWild:
12171             case BlackQueenSideCastleWild:
12172             case WhiteHSideCastleFR:
12173             case WhiteASideCastleFR:
12174             case BlackHSideCastleFR:
12175             case BlackASideCastleFR:
12176                 fromX = currentMoveString[0] - AAA;
12177                 fromY = currentMoveString[1] - ONE;
12178                 toX = currentMoveString[2] - AAA;
12179                 toY = currentMoveString[3] - ONE;
12180                 promoChar = currentMoveString[4];
12181                 break;
12182             case WhiteDrop:
12183             case BlackDrop:
12184                 fromX = next == WhiteDrop ?
12185                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12186                   (int) CharToPiece(ToLower(currentMoveString[0]));
12187                 fromY = DROP_RANK;
12188                 toX = currentMoveString[2] - AAA;
12189                 toY = currentMoveString[3] - ONE;
12190                 promoChar = 0;
12191                 break;
12192         }
12193         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12194         plyNr++;
12195         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12196         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12197         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12198         if(appData.findMirror) {
12199             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12200             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12201         }
12202     }
12203 }
12204
12205 /* Load the nth game from open file f */
12206 int
12207 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12208 {
12209     ChessMove cm;
12210     char buf[MSG_SIZ];
12211     int gn = gameNumber;
12212     ListGame *lg = NULL;
12213     int numPGNTags = 0;
12214     int err, pos = -1;
12215     GameMode oldGameMode;
12216     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12217
12218     if (appData.debugMode)
12219         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12220
12221     if (gameMode == Training )
12222         SetTrainingModeOff();
12223
12224     oldGameMode = gameMode;
12225     if (gameMode != BeginningOfGame) {
12226       Reset(FALSE, TRUE);
12227     }
12228
12229     gameFileFP = f;
12230     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12231         fclose(lastLoadGameFP);
12232     }
12233
12234     if (useList) {
12235         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12236
12237         if (lg) {
12238             fseek(f, lg->offset, 0);
12239             GameListHighlight(gameNumber);
12240             pos = lg->position;
12241             gn = 1;
12242         }
12243         else {
12244             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12245               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12246             else
12247             DisplayError(_("Game number out of range"), 0);
12248             return FALSE;
12249         }
12250     } else {
12251         GameListDestroy();
12252         if (fseek(f, 0, 0) == -1) {
12253             if (f == lastLoadGameFP ?
12254                 gameNumber == lastLoadGameNumber + 1 :
12255                 gameNumber == 1) {
12256                 gn = 1;
12257             } else {
12258                 DisplayError(_("Can't seek on game file"), 0);
12259                 return FALSE;
12260             }
12261         }
12262     }
12263     lastLoadGameFP = f;
12264     lastLoadGameNumber = gameNumber;
12265     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12266     lastLoadGameUseList = useList;
12267
12268     yynewfile(f);
12269
12270     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12271       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12272                 lg->gameInfo.black);
12273             DisplayTitle(buf);
12274     } else if (*title != NULLCHAR) {
12275         if (gameNumber > 1) {
12276           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12277             DisplayTitle(buf);
12278         } else {
12279             DisplayTitle(title);
12280         }
12281     }
12282
12283     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12284         gameMode = PlayFromGameFile;
12285         ModeHighlight();
12286     }
12287
12288     currentMove = forwardMostMove = backwardMostMove = 0;
12289     CopyBoard(boards[0], initialPosition);
12290     StopClocks();
12291
12292     /*
12293      * Skip the first gn-1 games in the file.
12294      * Also skip over anything that precedes an identifiable
12295      * start of game marker, to avoid being confused by
12296      * garbage at the start of the file.  Currently
12297      * recognized start of game markers are the move number "1",
12298      * the pattern "gnuchess .* game", the pattern
12299      * "^[#;%] [^ ]* game file", and a PGN tag block.
12300      * A game that starts with one of the latter two patterns
12301      * will also have a move number 1, possibly
12302      * following a position diagram.
12303      * 5-4-02: Let's try being more lenient and allowing a game to
12304      * start with an unnumbered move.  Does that break anything?
12305      */
12306     cm = lastLoadGameStart = EndOfFile;
12307     while (gn > 0) {
12308         yyboardindex = forwardMostMove;
12309         cm = (ChessMove) Myylex();
12310         switch (cm) {
12311           case EndOfFile:
12312             if (cmailMsgLoaded) {
12313                 nCmailGames = CMAIL_MAX_GAMES - gn;
12314             } else {
12315                 Reset(TRUE, TRUE);
12316                 DisplayError(_("Game not found in file"), 0);
12317             }
12318             return FALSE;
12319
12320           case GNUChessGame:
12321           case XBoardGame:
12322             gn--;
12323             lastLoadGameStart = cm;
12324             break;
12325
12326           case MoveNumberOne:
12327             switch (lastLoadGameStart) {
12328               case GNUChessGame:
12329               case XBoardGame:
12330               case PGNTag:
12331                 break;
12332               case MoveNumberOne:
12333               case EndOfFile:
12334                 gn--;           /* count this game */
12335                 lastLoadGameStart = cm;
12336                 break;
12337               default:
12338                 /* impossible */
12339                 break;
12340             }
12341             break;
12342
12343           case PGNTag:
12344             switch (lastLoadGameStart) {
12345               case GNUChessGame:
12346               case PGNTag:
12347               case MoveNumberOne:
12348               case EndOfFile:
12349                 gn--;           /* count this game */
12350                 lastLoadGameStart = cm;
12351                 break;
12352               case XBoardGame:
12353                 lastLoadGameStart = cm; /* game counted already */
12354                 break;
12355               default:
12356                 /* impossible */
12357                 break;
12358             }
12359             if (gn > 0) {
12360                 do {
12361                     yyboardindex = forwardMostMove;
12362                     cm = (ChessMove) Myylex();
12363                 } while (cm == PGNTag || cm == Comment);
12364             }
12365             break;
12366
12367           case WhiteWins:
12368           case BlackWins:
12369           case GameIsDrawn:
12370             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12371                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12372                     != CMAIL_OLD_RESULT) {
12373                     nCmailResults ++ ;
12374                     cmailResult[  CMAIL_MAX_GAMES
12375                                 - gn - 1] = CMAIL_OLD_RESULT;
12376                 }
12377             }
12378             break;
12379
12380           case NormalMove:
12381             /* Only a NormalMove can be at the start of a game
12382              * without a position diagram. */
12383             if (lastLoadGameStart == EndOfFile ) {
12384               gn--;
12385               lastLoadGameStart = MoveNumberOne;
12386             }
12387             break;
12388
12389           default:
12390             break;
12391         }
12392     }
12393
12394     if (appData.debugMode)
12395       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12396
12397     if (cm == XBoardGame) {
12398         /* Skip any header junk before position diagram and/or move 1 */
12399         for (;;) {
12400             yyboardindex = forwardMostMove;
12401             cm = (ChessMove) Myylex();
12402
12403             if (cm == EndOfFile ||
12404                 cm == GNUChessGame || cm == XBoardGame) {
12405                 /* Empty game; pretend end-of-file and handle later */
12406                 cm = EndOfFile;
12407                 break;
12408             }
12409
12410             if (cm == MoveNumberOne || cm == PositionDiagram ||
12411                 cm == PGNTag || cm == Comment)
12412               break;
12413         }
12414     } else if (cm == GNUChessGame) {
12415         if (gameInfo.event != NULL) {
12416             free(gameInfo.event);
12417         }
12418         gameInfo.event = StrSave(yy_text);
12419     }
12420
12421     startedFromSetupPosition = FALSE;
12422     while (cm == PGNTag) {
12423         if (appData.debugMode)
12424           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12425         err = ParsePGNTag(yy_text, &gameInfo);
12426         if (!err) numPGNTags++;
12427
12428         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12429         if(gameInfo.variant != oldVariant) {
12430             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12431             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12432             InitPosition(TRUE);
12433             oldVariant = gameInfo.variant;
12434             if (appData.debugMode)
12435               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12436         }
12437
12438
12439         if (gameInfo.fen != NULL) {
12440           Board initial_position;
12441           startedFromSetupPosition = TRUE;
12442           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12443             Reset(TRUE, TRUE);
12444             DisplayError(_("Bad FEN position in file"), 0);
12445             return FALSE;
12446           }
12447           CopyBoard(boards[0], initial_position);
12448           if (blackPlaysFirst) {
12449             currentMove = forwardMostMove = backwardMostMove = 1;
12450             CopyBoard(boards[1], initial_position);
12451             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12452             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12453             timeRemaining[0][1] = whiteTimeRemaining;
12454             timeRemaining[1][1] = blackTimeRemaining;
12455             if (commentList[0] != NULL) {
12456               commentList[1] = commentList[0];
12457               commentList[0] = NULL;
12458             }
12459           } else {
12460             currentMove = forwardMostMove = backwardMostMove = 0;
12461           }
12462           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12463           {   int i;
12464               initialRulePlies = FENrulePlies;
12465               for( i=0; i< nrCastlingRights; i++ )
12466                   initialRights[i] = initial_position[CASTLING][i];
12467           }
12468           yyboardindex = forwardMostMove;
12469           free(gameInfo.fen);
12470           gameInfo.fen = NULL;
12471         }
12472
12473         yyboardindex = forwardMostMove;
12474         cm = (ChessMove) Myylex();
12475
12476         /* Handle comments interspersed among the tags */
12477         while (cm == Comment) {
12478             char *p;
12479             if (appData.debugMode)
12480               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12481             p = yy_text;
12482             AppendComment(currentMove, p, FALSE);
12483             yyboardindex = forwardMostMove;
12484             cm = (ChessMove) Myylex();
12485         }
12486     }
12487
12488     /* don't rely on existence of Event tag since if game was
12489      * pasted from clipboard the Event tag may not exist
12490      */
12491     if (numPGNTags > 0){
12492         char *tags;
12493         if (gameInfo.variant == VariantNormal) {
12494           VariantClass v = StringToVariant(gameInfo.event);
12495           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12496           if(v < VariantShogi) gameInfo.variant = v;
12497         }
12498         if (!matchMode) {
12499           if( appData.autoDisplayTags ) {
12500             tags = PGNTags(&gameInfo);
12501             TagsPopUp(tags, CmailMsg());
12502             free(tags);
12503           }
12504         }
12505     } else {
12506         /* Make something up, but don't display it now */
12507         SetGameInfo();
12508         TagsPopDown();
12509     }
12510
12511     if (cm == PositionDiagram) {
12512         int i, j;
12513         char *p;
12514         Board initial_position;
12515
12516         if (appData.debugMode)
12517           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12518
12519         if (!startedFromSetupPosition) {
12520             p = yy_text;
12521             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12522               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12523                 switch (*p) {
12524                   case '{':
12525                   case '[':
12526                   case '-':
12527                   case ' ':
12528                   case '\t':
12529                   case '\n':
12530                   case '\r':
12531                     break;
12532                   default:
12533                     initial_position[i][j++] = CharToPiece(*p);
12534                     break;
12535                 }
12536             while (*p == ' ' || *p == '\t' ||
12537                    *p == '\n' || *p == '\r') p++;
12538
12539             if (strncmp(p, "black", strlen("black"))==0)
12540               blackPlaysFirst = TRUE;
12541             else
12542               blackPlaysFirst = FALSE;
12543             startedFromSetupPosition = TRUE;
12544
12545             CopyBoard(boards[0], initial_position);
12546             if (blackPlaysFirst) {
12547                 currentMove = forwardMostMove = backwardMostMove = 1;
12548                 CopyBoard(boards[1], initial_position);
12549                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12550                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12551                 timeRemaining[0][1] = whiteTimeRemaining;
12552                 timeRemaining[1][1] = blackTimeRemaining;
12553                 if (commentList[0] != NULL) {
12554                     commentList[1] = commentList[0];
12555                     commentList[0] = NULL;
12556                 }
12557             } else {
12558                 currentMove = forwardMostMove = backwardMostMove = 0;
12559             }
12560         }
12561         yyboardindex = forwardMostMove;
12562         cm = (ChessMove) Myylex();
12563     }
12564
12565   if(!creatingBook) {
12566     if (first.pr == NoProc) {
12567         StartChessProgram(&first);
12568     }
12569     InitChessProgram(&first, FALSE);
12570     SendToProgram("force\n", &first);
12571     if (startedFromSetupPosition) {
12572         SendBoard(&first, forwardMostMove);
12573     if (appData.debugMode) {
12574         fprintf(debugFP, "Load Game\n");
12575     }
12576         DisplayBothClocks();
12577     }
12578   }
12579
12580     /* [HGM] server: flag to write setup moves in broadcast file as one */
12581     loadFlag = appData.suppressLoadMoves;
12582
12583     while (cm == Comment) {
12584         char *p;
12585         if (appData.debugMode)
12586           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12587         p = yy_text;
12588         AppendComment(currentMove, p, FALSE);
12589         yyboardindex = forwardMostMove;
12590         cm = (ChessMove) Myylex();
12591     }
12592
12593     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12594         cm == WhiteWins || cm == BlackWins ||
12595         cm == GameIsDrawn || cm == GameUnfinished) {
12596         DisplayMessage("", _("No moves in game"));
12597         if (cmailMsgLoaded) {
12598             if (appData.debugMode)
12599               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12600             ClearHighlights();
12601             flipView = FALSE;
12602         }
12603         DrawPosition(FALSE, boards[currentMove]);
12604         DisplayBothClocks();
12605         gameMode = EditGame;
12606         ModeHighlight();
12607         gameFileFP = NULL;
12608         cmailOldMove = 0;
12609         return TRUE;
12610     }
12611
12612     // [HGM] PV info: routine tests if comment empty
12613     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12614         DisplayComment(currentMove - 1, commentList[currentMove]);
12615     }
12616     if (!matchMode && appData.timeDelay != 0)
12617       DrawPosition(FALSE, boards[currentMove]);
12618
12619     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12620       programStats.ok_to_send = 1;
12621     }
12622
12623     /* if the first token after the PGN tags is a move
12624      * and not move number 1, retrieve it from the parser
12625      */
12626     if (cm != MoveNumberOne)
12627         LoadGameOneMove(cm);
12628
12629     /* load the remaining moves from the file */
12630     while (LoadGameOneMove(EndOfFile)) {
12631       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12632       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12633     }
12634
12635     /* rewind to the start of the game */
12636     currentMove = backwardMostMove;
12637
12638     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12639
12640     if (oldGameMode == AnalyzeFile) {
12641       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12642       AnalyzeFileEvent();
12643     } else
12644     if (oldGameMode == AnalyzeMode) {
12645       AnalyzeFileEvent();
12646     }
12647
12648     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12649         long int w, b; // [HGM] adjourn: restore saved clock times
12650         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12651         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12652             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12653             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12654         }
12655     }
12656
12657     if(creatingBook) return TRUE;
12658     if (!matchMode && pos > 0) {
12659         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12660     } else
12661     if (matchMode || appData.timeDelay == 0) {
12662       ToEndEvent();
12663     } else if (appData.timeDelay > 0) {
12664       AutoPlayGameLoop();
12665     }
12666
12667     if (appData.debugMode)
12668         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12669
12670     loadFlag = 0; /* [HGM] true game starts */
12671     return TRUE;
12672 }
12673
12674 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12675 int
12676 ReloadPosition (int offset)
12677 {
12678     int positionNumber = lastLoadPositionNumber + offset;
12679     if (lastLoadPositionFP == NULL) {
12680         DisplayError(_("No position has been loaded yet"), 0);
12681         return FALSE;
12682     }
12683     if (positionNumber <= 0) {
12684         DisplayError(_("Can't back up any further"), 0);
12685         return FALSE;
12686     }
12687     return LoadPosition(lastLoadPositionFP, positionNumber,
12688                         lastLoadPositionTitle);
12689 }
12690
12691 /* Load the nth position from the given file */
12692 int
12693 LoadPositionFromFile (char *filename, int n, char *title)
12694 {
12695     FILE *f;
12696     char buf[MSG_SIZ];
12697
12698     if (strcmp(filename, "-") == 0) {
12699         return LoadPosition(stdin, n, "stdin");
12700     } else {
12701         f = fopen(filename, "rb");
12702         if (f == NULL) {
12703             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12704             DisplayError(buf, errno);
12705             return FALSE;
12706         } else {
12707             return LoadPosition(f, n, title);
12708         }
12709     }
12710 }
12711
12712 /* Load the nth position from the given open file, and close it */
12713 int
12714 LoadPosition (FILE *f, int positionNumber, char *title)
12715 {
12716     char *p, line[MSG_SIZ];
12717     Board initial_position;
12718     int i, j, fenMode, pn;
12719
12720     if (gameMode == Training )
12721         SetTrainingModeOff();
12722
12723     if (gameMode != BeginningOfGame) {
12724         Reset(FALSE, TRUE);
12725     }
12726     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12727         fclose(lastLoadPositionFP);
12728     }
12729     if (positionNumber == 0) positionNumber = 1;
12730     lastLoadPositionFP = f;
12731     lastLoadPositionNumber = positionNumber;
12732     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12733     if (first.pr == NoProc && !appData.noChessProgram) {
12734       StartChessProgram(&first);
12735       InitChessProgram(&first, FALSE);
12736     }
12737     pn = positionNumber;
12738     if (positionNumber < 0) {
12739         /* Negative position number means to seek to that byte offset */
12740         if (fseek(f, -positionNumber, 0) == -1) {
12741             DisplayError(_("Can't seek on position file"), 0);
12742             return FALSE;
12743         };
12744         pn = 1;
12745     } else {
12746         if (fseek(f, 0, 0) == -1) {
12747             if (f == lastLoadPositionFP ?
12748                 positionNumber == lastLoadPositionNumber + 1 :
12749                 positionNumber == 1) {
12750                 pn = 1;
12751             } else {
12752                 DisplayError(_("Can't seek on position file"), 0);
12753                 return FALSE;
12754             }
12755         }
12756     }
12757     /* See if this file is FEN or old-style xboard */
12758     if (fgets(line, MSG_SIZ, f) == NULL) {
12759         DisplayError(_("Position not found in file"), 0);
12760         return FALSE;
12761     }
12762     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12763     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12764
12765     if (pn >= 2) {
12766         if (fenMode || line[0] == '#') pn--;
12767         while (pn > 0) {
12768             /* skip positions before number pn */
12769             if (fgets(line, MSG_SIZ, f) == NULL) {
12770                 Reset(TRUE, TRUE);
12771                 DisplayError(_("Position not found in file"), 0);
12772                 return FALSE;
12773             }
12774             if (fenMode || line[0] == '#') pn--;
12775         }
12776     }
12777
12778     if (fenMode) {
12779         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12780             DisplayError(_("Bad FEN position in file"), 0);
12781             return FALSE;
12782         }
12783     } else {
12784         (void) fgets(line, MSG_SIZ, f);
12785         (void) fgets(line, MSG_SIZ, f);
12786
12787         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12788             (void) fgets(line, MSG_SIZ, f);
12789             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12790                 if (*p == ' ')
12791                   continue;
12792                 initial_position[i][j++] = CharToPiece(*p);
12793             }
12794         }
12795
12796         blackPlaysFirst = FALSE;
12797         if (!feof(f)) {
12798             (void) fgets(line, MSG_SIZ, f);
12799             if (strncmp(line, "black", strlen("black"))==0)
12800               blackPlaysFirst = TRUE;
12801         }
12802     }
12803     startedFromSetupPosition = TRUE;
12804
12805     CopyBoard(boards[0], initial_position);
12806     if (blackPlaysFirst) {
12807         currentMove = forwardMostMove = backwardMostMove = 1;
12808         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12809         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12810         CopyBoard(boards[1], initial_position);
12811         DisplayMessage("", _("Black to play"));
12812     } else {
12813         currentMove = forwardMostMove = backwardMostMove = 0;
12814         DisplayMessage("", _("White to play"));
12815     }
12816     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12817     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12818         SendToProgram("force\n", &first);
12819         SendBoard(&first, forwardMostMove);
12820     }
12821     if (appData.debugMode) {
12822 int i, j;
12823   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12824   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12825         fprintf(debugFP, "Load Position\n");
12826     }
12827
12828     if (positionNumber > 1) {
12829       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12830         DisplayTitle(line);
12831     } else {
12832         DisplayTitle(title);
12833     }
12834     gameMode = EditGame;
12835     ModeHighlight();
12836     ResetClocks();
12837     timeRemaining[0][1] = whiteTimeRemaining;
12838     timeRemaining[1][1] = blackTimeRemaining;
12839     DrawPosition(FALSE, boards[currentMove]);
12840
12841     return TRUE;
12842 }
12843
12844
12845 void
12846 CopyPlayerNameIntoFileName (char **dest, char *src)
12847 {
12848     while (*src != NULLCHAR && *src != ',') {
12849         if (*src == ' ') {
12850             *(*dest)++ = '_';
12851             src++;
12852         } else {
12853             *(*dest)++ = *src++;
12854         }
12855     }
12856 }
12857
12858 char *
12859 DefaultFileName (char *ext)
12860 {
12861     static char def[MSG_SIZ];
12862     char *p;
12863
12864     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12865         p = def;
12866         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12867         *p++ = '-';
12868         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12869         *p++ = '.';
12870         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12871     } else {
12872         def[0] = NULLCHAR;
12873     }
12874     return def;
12875 }
12876
12877 /* Save the current game to the given file */
12878 int
12879 SaveGameToFile (char *filename, int append)
12880 {
12881     FILE *f;
12882     char buf[MSG_SIZ];
12883     int result, i, t,tot=0;
12884
12885     if (strcmp(filename, "-") == 0) {
12886         return SaveGame(stdout, 0, NULL);
12887     } else {
12888         for(i=0; i<10; i++) { // upto 10 tries
12889              f = fopen(filename, append ? "a" : "w");
12890              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12891              if(f || errno != 13) break;
12892              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12893              tot += t;
12894         }
12895         if (f == NULL) {
12896             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12897             DisplayError(buf, errno);
12898             return FALSE;
12899         } else {
12900             safeStrCpy(buf, lastMsg, MSG_SIZ);
12901             DisplayMessage(_("Waiting for access to save file"), "");
12902             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12903             DisplayMessage(_("Saving game"), "");
12904             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12905             result = SaveGame(f, 0, NULL);
12906             DisplayMessage(buf, "");
12907             return result;
12908         }
12909     }
12910 }
12911
12912 char *
12913 SavePart (char *str)
12914 {
12915     static char buf[MSG_SIZ];
12916     char *p;
12917
12918     p = strchr(str, ' ');
12919     if (p == NULL) return str;
12920     strncpy(buf, str, p - str);
12921     buf[p - str] = NULLCHAR;
12922     return buf;
12923 }
12924
12925 #define PGN_MAX_LINE 75
12926
12927 #define PGN_SIDE_WHITE  0
12928 #define PGN_SIDE_BLACK  1
12929
12930 static int
12931 FindFirstMoveOutOfBook (int side)
12932 {
12933     int result = -1;
12934
12935     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12936         int index = backwardMostMove;
12937         int has_book_hit = 0;
12938
12939         if( (index % 2) != side ) {
12940             index++;
12941         }
12942
12943         while( index < forwardMostMove ) {
12944             /* Check to see if engine is in book */
12945             int depth = pvInfoList[index].depth;
12946             int score = pvInfoList[index].score;
12947             int in_book = 0;
12948
12949             if( depth <= 2 ) {
12950                 in_book = 1;
12951             }
12952             else if( score == 0 && depth == 63 ) {
12953                 in_book = 1; /* Zappa */
12954             }
12955             else if( score == 2 && depth == 99 ) {
12956                 in_book = 1; /* Abrok */
12957             }
12958
12959             has_book_hit += in_book;
12960
12961             if( ! in_book ) {
12962                 result = index;
12963
12964                 break;
12965             }
12966
12967             index += 2;
12968         }
12969     }
12970
12971     return result;
12972 }
12973
12974 void
12975 GetOutOfBookInfo (char * buf)
12976 {
12977     int oob[2];
12978     int i;
12979     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12980
12981     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12982     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12983
12984     *buf = '\0';
12985
12986     if( oob[0] >= 0 || oob[1] >= 0 ) {
12987         for( i=0; i<2; i++ ) {
12988             int idx = oob[i];
12989
12990             if( idx >= 0 ) {
12991                 if( i > 0 && oob[0] >= 0 ) {
12992                     strcat( buf, "   " );
12993                 }
12994
12995                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12996                 sprintf( buf+strlen(buf), "%s%.2f",
12997                     pvInfoList[idx].score >= 0 ? "+" : "",
12998                     pvInfoList[idx].score / 100.0 );
12999             }
13000         }
13001     }
13002 }
13003
13004 /* Save game in PGN style and close the file */
13005 int
13006 SaveGamePGN (FILE *f)
13007 {
13008     int i, offset, linelen, newblock;
13009 //    char *movetext;
13010     char numtext[32];
13011     int movelen, numlen, blank;
13012     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13013
13014     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13015
13016     PrintPGNTags(f, &gameInfo);
13017
13018     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13019
13020     if (backwardMostMove > 0 || startedFromSetupPosition) {
13021         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13022         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13023         fprintf(f, "\n{--------------\n");
13024         PrintPosition(f, backwardMostMove);
13025         fprintf(f, "--------------}\n");
13026         free(fen);
13027     }
13028     else {
13029         /* [AS] Out of book annotation */
13030         if( appData.saveOutOfBookInfo ) {
13031             char buf[64];
13032
13033             GetOutOfBookInfo( buf );
13034
13035             if( buf[0] != '\0' ) {
13036                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13037             }
13038         }
13039
13040         fprintf(f, "\n");
13041     }
13042
13043     i = backwardMostMove;
13044     linelen = 0;
13045     newblock = TRUE;
13046
13047     while (i < forwardMostMove) {
13048         /* Print comments preceding this move */
13049         if (commentList[i] != NULL) {
13050             if (linelen > 0) fprintf(f, "\n");
13051             fprintf(f, "%s", commentList[i]);
13052             linelen = 0;
13053             newblock = TRUE;
13054         }
13055
13056         /* Format move number */
13057         if ((i % 2) == 0)
13058           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13059         else
13060           if (newblock)
13061             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13062           else
13063             numtext[0] = NULLCHAR;
13064
13065         numlen = strlen(numtext);
13066         newblock = FALSE;
13067
13068         /* Print move number */
13069         blank = linelen > 0 && numlen > 0;
13070         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13071             fprintf(f, "\n");
13072             linelen = 0;
13073             blank = 0;
13074         }
13075         if (blank) {
13076             fprintf(f, " ");
13077             linelen++;
13078         }
13079         fprintf(f, "%s", numtext);
13080         linelen += numlen;
13081
13082         /* Get move */
13083         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13084         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13085
13086         /* Print move */
13087         blank = linelen > 0 && movelen > 0;
13088         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13089             fprintf(f, "\n");
13090             linelen = 0;
13091             blank = 0;
13092         }
13093         if (blank) {
13094             fprintf(f, " ");
13095             linelen++;
13096         }
13097         fprintf(f, "%s", move_buffer);
13098         linelen += movelen;
13099
13100         /* [AS] Add PV info if present */
13101         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13102             /* [HGM] add time */
13103             char buf[MSG_SIZ]; int seconds;
13104
13105             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13106
13107             if( seconds <= 0)
13108               buf[0] = 0;
13109             else
13110               if( seconds < 30 )
13111                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13112               else
13113                 {
13114                   seconds = (seconds + 4)/10; // round to full seconds
13115                   if( seconds < 60 )
13116                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13117                   else
13118                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13119                 }
13120
13121             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13122                       pvInfoList[i].score >= 0 ? "+" : "",
13123                       pvInfoList[i].score / 100.0,
13124                       pvInfoList[i].depth,
13125                       buf );
13126
13127             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13128
13129             /* Print score/depth */
13130             blank = linelen > 0 && movelen > 0;
13131             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13132                 fprintf(f, "\n");
13133                 linelen = 0;
13134                 blank = 0;
13135             }
13136             if (blank) {
13137                 fprintf(f, " ");
13138                 linelen++;
13139             }
13140             fprintf(f, "%s", move_buffer);
13141             linelen += movelen;
13142         }
13143
13144         i++;
13145     }
13146
13147     /* Start a new line */
13148     if (linelen > 0) fprintf(f, "\n");
13149
13150     /* Print comments after last move */
13151     if (commentList[i] != NULL) {
13152         fprintf(f, "%s\n", commentList[i]);
13153     }
13154
13155     /* Print result */
13156     if (gameInfo.resultDetails != NULL &&
13157         gameInfo.resultDetails[0] != NULLCHAR) {
13158         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13159         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13160            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13161             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13162         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13163     } else {
13164         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13165     }
13166
13167     fclose(f);
13168     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13169     return TRUE;
13170 }
13171
13172 /* Save game in old style and close the file */
13173 int
13174 SaveGameOldStyle (FILE *f)
13175 {
13176     int i, offset;
13177     time_t tm;
13178
13179     tm = time((time_t *) NULL);
13180
13181     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13182     PrintOpponents(f);
13183
13184     if (backwardMostMove > 0 || startedFromSetupPosition) {
13185         fprintf(f, "\n[--------------\n");
13186         PrintPosition(f, backwardMostMove);
13187         fprintf(f, "--------------]\n");
13188     } else {
13189         fprintf(f, "\n");
13190     }
13191
13192     i = backwardMostMove;
13193     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13194
13195     while (i < forwardMostMove) {
13196         if (commentList[i] != NULL) {
13197             fprintf(f, "[%s]\n", commentList[i]);
13198         }
13199
13200         if ((i % 2) == 1) {
13201             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13202             i++;
13203         } else {
13204             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13205             i++;
13206             if (commentList[i] != NULL) {
13207                 fprintf(f, "\n");
13208                 continue;
13209             }
13210             if (i >= forwardMostMove) {
13211                 fprintf(f, "\n");
13212                 break;
13213             }
13214             fprintf(f, "%s\n", parseList[i]);
13215             i++;
13216         }
13217     }
13218
13219     if (commentList[i] != NULL) {
13220         fprintf(f, "[%s]\n", commentList[i]);
13221     }
13222
13223     /* This isn't really the old style, but it's close enough */
13224     if (gameInfo.resultDetails != NULL &&
13225         gameInfo.resultDetails[0] != NULLCHAR) {
13226         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13227                 gameInfo.resultDetails);
13228     } else {
13229         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13230     }
13231
13232     fclose(f);
13233     return TRUE;
13234 }
13235
13236 /* Save the current game to open file f and close the file */
13237 int
13238 SaveGame (FILE *f, int dummy, char *dummy2)
13239 {
13240     if (gameMode == EditPosition) EditPositionDone(TRUE);
13241     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13242     if (appData.oldSaveStyle)
13243       return SaveGameOldStyle(f);
13244     else
13245       return SaveGamePGN(f);
13246 }
13247
13248 /* Save the current position to the given file */
13249 int
13250 SavePositionToFile (char *filename)
13251 {
13252     FILE *f;
13253     char buf[MSG_SIZ];
13254
13255     if (strcmp(filename, "-") == 0) {
13256         return SavePosition(stdout, 0, NULL);
13257     } else {
13258         f = fopen(filename, "a");
13259         if (f == NULL) {
13260             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13261             DisplayError(buf, errno);
13262             return FALSE;
13263         } else {
13264             safeStrCpy(buf, lastMsg, MSG_SIZ);
13265             DisplayMessage(_("Waiting for access to save file"), "");
13266             flock(fileno(f), LOCK_EX); // [HGM] lock
13267             DisplayMessage(_("Saving position"), "");
13268             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13269             SavePosition(f, 0, NULL);
13270             DisplayMessage(buf, "");
13271             return TRUE;
13272         }
13273     }
13274 }
13275
13276 /* Save the current position to the given open file and close the file */
13277 int
13278 SavePosition (FILE *f, int dummy, char *dummy2)
13279 {
13280     time_t tm;
13281     char *fen;
13282
13283     if (gameMode == EditPosition) EditPositionDone(TRUE);
13284     if (appData.oldSaveStyle) {
13285         tm = time((time_t *) NULL);
13286
13287         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13288         PrintOpponents(f);
13289         fprintf(f, "[--------------\n");
13290         PrintPosition(f, currentMove);
13291         fprintf(f, "--------------]\n");
13292     } else {
13293         fen = PositionToFEN(currentMove, NULL, 1);
13294         fprintf(f, "%s\n", fen);
13295         free(fen);
13296     }
13297     fclose(f);
13298     return TRUE;
13299 }
13300
13301 void
13302 ReloadCmailMsgEvent (int unregister)
13303 {
13304 #if !WIN32
13305     static char *inFilename = NULL;
13306     static char *outFilename;
13307     int i;
13308     struct stat inbuf, outbuf;
13309     int status;
13310
13311     /* Any registered moves are unregistered if unregister is set, */
13312     /* i.e. invoked by the signal handler */
13313     if (unregister) {
13314         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13315             cmailMoveRegistered[i] = FALSE;
13316             if (cmailCommentList[i] != NULL) {
13317                 free(cmailCommentList[i]);
13318                 cmailCommentList[i] = NULL;
13319             }
13320         }
13321         nCmailMovesRegistered = 0;
13322     }
13323
13324     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13325         cmailResult[i] = CMAIL_NOT_RESULT;
13326     }
13327     nCmailResults = 0;
13328
13329     if (inFilename == NULL) {
13330         /* Because the filenames are static they only get malloced once  */
13331         /* and they never get freed                                      */
13332         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13333         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13334
13335         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13336         sprintf(outFilename, "%s.out", appData.cmailGameName);
13337     }
13338
13339     status = stat(outFilename, &outbuf);
13340     if (status < 0) {
13341         cmailMailedMove = FALSE;
13342     } else {
13343         status = stat(inFilename, &inbuf);
13344         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13345     }
13346
13347     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13348        counts the games, notes how each one terminated, etc.
13349
13350        It would be nice to remove this kludge and instead gather all
13351        the information while building the game list.  (And to keep it
13352        in the game list nodes instead of having a bunch of fixed-size
13353        parallel arrays.)  Note this will require getting each game's
13354        termination from the PGN tags, as the game list builder does
13355        not process the game moves.  --mann
13356        */
13357     cmailMsgLoaded = TRUE;
13358     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13359
13360     /* Load first game in the file or popup game menu */
13361     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13362
13363 #endif /* !WIN32 */
13364     return;
13365 }
13366
13367 int
13368 RegisterMove ()
13369 {
13370     FILE *f;
13371     char string[MSG_SIZ];
13372
13373     if (   cmailMailedMove
13374         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13375         return TRUE;            /* Allow free viewing  */
13376     }
13377
13378     /* Unregister move to ensure that we don't leave RegisterMove        */
13379     /* with the move registered when the conditions for registering no   */
13380     /* longer hold                                                       */
13381     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13382         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13383         nCmailMovesRegistered --;
13384
13385         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13386           {
13387               free(cmailCommentList[lastLoadGameNumber - 1]);
13388               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13389           }
13390     }
13391
13392     if (cmailOldMove == -1) {
13393         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13394         return FALSE;
13395     }
13396
13397     if (currentMove > cmailOldMove + 1) {
13398         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13399         return FALSE;
13400     }
13401
13402     if (currentMove < cmailOldMove) {
13403         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13404         return FALSE;
13405     }
13406
13407     if (forwardMostMove > currentMove) {
13408         /* Silently truncate extra moves */
13409         TruncateGame();
13410     }
13411
13412     if (   (currentMove == cmailOldMove + 1)
13413         || (   (currentMove == cmailOldMove)
13414             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13415                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13416         if (gameInfo.result != GameUnfinished) {
13417             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13418         }
13419
13420         if (commentList[currentMove] != NULL) {
13421             cmailCommentList[lastLoadGameNumber - 1]
13422               = StrSave(commentList[currentMove]);
13423         }
13424         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13425
13426         if (appData.debugMode)
13427           fprintf(debugFP, "Saving %s for game %d\n",
13428                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13429
13430         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13431
13432         f = fopen(string, "w");
13433         if (appData.oldSaveStyle) {
13434             SaveGameOldStyle(f); /* also closes the file */
13435
13436             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13437             f = fopen(string, "w");
13438             SavePosition(f, 0, NULL); /* also closes the file */
13439         } else {
13440             fprintf(f, "{--------------\n");
13441             PrintPosition(f, currentMove);
13442             fprintf(f, "--------------}\n\n");
13443
13444             SaveGame(f, 0, NULL); /* also closes the file*/
13445         }
13446
13447         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13448         nCmailMovesRegistered ++;
13449     } else if (nCmailGames == 1) {
13450         DisplayError(_("You have not made a move yet"), 0);
13451         return FALSE;
13452     }
13453
13454     return TRUE;
13455 }
13456
13457 void
13458 MailMoveEvent ()
13459 {
13460 #if !WIN32
13461     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13462     FILE *commandOutput;
13463     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13464     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13465     int nBuffers;
13466     int i;
13467     int archived;
13468     char *arcDir;
13469
13470     if (! cmailMsgLoaded) {
13471         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13472         return;
13473     }
13474
13475     if (nCmailGames == nCmailResults) {
13476         DisplayError(_("No unfinished games"), 0);
13477         return;
13478     }
13479
13480 #if CMAIL_PROHIBIT_REMAIL
13481     if (cmailMailedMove) {
13482       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);
13483         DisplayError(msg, 0);
13484         return;
13485     }
13486 #endif
13487
13488     if (! (cmailMailedMove || RegisterMove())) return;
13489
13490     if (   cmailMailedMove
13491         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13492       snprintf(string, MSG_SIZ, partCommandString,
13493                appData.debugMode ? " -v" : "", appData.cmailGameName);
13494         commandOutput = popen(string, "r");
13495
13496         if (commandOutput == NULL) {
13497             DisplayError(_("Failed to invoke cmail"), 0);
13498         } else {
13499             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13500                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13501             }
13502             if (nBuffers > 1) {
13503                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13504                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13505                 nBytes = MSG_SIZ - 1;
13506             } else {
13507                 (void) memcpy(msg, buffer, nBytes);
13508             }
13509             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13510
13511             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13512                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13513
13514                 archived = TRUE;
13515                 for (i = 0; i < nCmailGames; i ++) {
13516                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13517                         archived = FALSE;
13518                     }
13519                 }
13520                 if (   archived
13521                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13522                         != NULL)) {
13523                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13524                            arcDir,
13525                            appData.cmailGameName,
13526                            gameInfo.date);
13527                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13528                     cmailMsgLoaded = FALSE;
13529                 }
13530             }
13531
13532             DisplayInformation(msg);
13533             pclose(commandOutput);
13534         }
13535     } else {
13536         if ((*cmailMsg) != '\0') {
13537             DisplayInformation(cmailMsg);
13538         }
13539     }
13540
13541     return;
13542 #endif /* !WIN32 */
13543 }
13544
13545 char *
13546 CmailMsg ()
13547 {
13548 #if WIN32
13549     return NULL;
13550 #else
13551     int  prependComma = 0;
13552     char number[5];
13553     char string[MSG_SIZ];       /* Space for game-list */
13554     int  i;
13555
13556     if (!cmailMsgLoaded) return "";
13557
13558     if (cmailMailedMove) {
13559       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13560     } else {
13561         /* Create a list of games left */
13562       snprintf(string, MSG_SIZ, "[");
13563         for (i = 0; i < nCmailGames; i ++) {
13564             if (! (   cmailMoveRegistered[i]
13565                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13566                 if (prependComma) {
13567                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13568                 } else {
13569                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13570                     prependComma = 1;
13571                 }
13572
13573                 strcat(string, number);
13574             }
13575         }
13576         strcat(string, "]");
13577
13578         if (nCmailMovesRegistered + nCmailResults == 0) {
13579             switch (nCmailGames) {
13580               case 1:
13581                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13582                 break;
13583
13584               case 2:
13585                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13586                 break;
13587
13588               default:
13589                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13590                          nCmailGames);
13591                 break;
13592             }
13593         } else {
13594             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13595               case 1:
13596                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13597                          string);
13598                 break;
13599
13600               case 0:
13601                 if (nCmailResults == nCmailGames) {
13602                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13603                 } else {
13604                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13605                 }
13606                 break;
13607
13608               default:
13609                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13610                          string);
13611             }
13612         }
13613     }
13614     return cmailMsg;
13615 #endif /* WIN32 */
13616 }
13617
13618 void
13619 ResetGameEvent ()
13620 {
13621     if (gameMode == Training)
13622       SetTrainingModeOff();
13623
13624     Reset(TRUE, TRUE);
13625     cmailMsgLoaded = FALSE;
13626     if (appData.icsActive) {
13627       SendToICS(ics_prefix);
13628       SendToICS("refresh\n");
13629     }
13630 }
13631
13632 void
13633 ExitEvent (int status)
13634 {
13635     exiting++;
13636     if (exiting > 2) {
13637       /* Give up on clean exit */
13638       exit(status);
13639     }
13640     if (exiting > 1) {
13641       /* Keep trying for clean exit */
13642       return;
13643     }
13644
13645     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13646
13647     if (telnetISR != NULL) {
13648       RemoveInputSource(telnetISR);
13649     }
13650     if (icsPR != NoProc) {
13651       DestroyChildProcess(icsPR, TRUE);
13652     }
13653
13654     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13655     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13656
13657     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13658     /* make sure this other one finishes before killing it!                  */
13659     if(endingGame) { int count = 0;
13660         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13661         while(endingGame && count++ < 10) DoSleep(1);
13662         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13663     }
13664
13665     /* Kill off chess programs */
13666     if (first.pr != NoProc) {
13667         ExitAnalyzeMode();
13668
13669         DoSleep( appData.delayBeforeQuit );
13670         SendToProgram("quit\n", &first);
13671         DoSleep( appData.delayAfterQuit );
13672         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13673     }
13674     if (second.pr != NoProc) {
13675         DoSleep( appData.delayBeforeQuit );
13676         SendToProgram("quit\n", &second);
13677         DoSleep( appData.delayAfterQuit );
13678         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13679     }
13680     if (first.isr != NULL) {
13681         RemoveInputSource(first.isr);
13682     }
13683     if (second.isr != NULL) {
13684         RemoveInputSource(second.isr);
13685     }
13686
13687     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13688     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13689
13690     ShutDownFrontEnd();
13691     exit(status);
13692 }
13693
13694 void
13695 PauseEngine (ChessProgramState *cps)
13696 {
13697     SendToProgram("pause\n", cps);
13698     cps->pause = 2;
13699 }
13700
13701 void
13702 UnPauseEngine (ChessProgramState *cps)
13703 {
13704     SendToProgram("resume\n", cps);
13705     cps->pause = 1;
13706 }
13707
13708 void
13709 PauseEvent ()
13710 {
13711     if (appData.debugMode)
13712         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13713     if (pausing) {
13714         pausing = FALSE;
13715         ModeHighlight();
13716         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13717             StartClocks();
13718             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13719                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13720                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13721             }
13722             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13723             HandleMachineMove(stashedInputMove, stalledEngine);
13724             stalledEngine = NULL;
13725             return;
13726         }
13727         if (gameMode == MachinePlaysWhite ||
13728             gameMode == TwoMachinesPlay   ||
13729             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13730             if(first.pause)  UnPauseEngine(&first);
13731             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13732             if(second.pause) UnPauseEngine(&second);
13733             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13734             StartClocks();
13735         } else {
13736             DisplayBothClocks();
13737         }
13738         if (gameMode == PlayFromGameFile) {
13739             if (appData.timeDelay >= 0)
13740                 AutoPlayGameLoop();
13741         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13742             Reset(FALSE, TRUE);
13743             SendToICS(ics_prefix);
13744             SendToICS("refresh\n");
13745         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13746             ForwardInner(forwardMostMove);
13747         }
13748         pauseExamInvalid = FALSE;
13749     } else {
13750         switch (gameMode) {
13751           default:
13752             return;
13753           case IcsExamining:
13754             pauseExamForwardMostMove = forwardMostMove;
13755             pauseExamInvalid = FALSE;
13756             /* fall through */
13757           case IcsObserving:
13758           case IcsPlayingWhite:
13759           case IcsPlayingBlack:
13760             pausing = TRUE;
13761             ModeHighlight();
13762             return;
13763           case PlayFromGameFile:
13764             (void) StopLoadGameTimer();
13765             pausing = TRUE;
13766             ModeHighlight();
13767             break;
13768           case BeginningOfGame:
13769             if (appData.icsActive) return;
13770             /* else fall through */
13771           case MachinePlaysWhite:
13772           case MachinePlaysBlack:
13773           case TwoMachinesPlay:
13774             if (forwardMostMove == 0)
13775               return;           /* don't pause if no one has moved */
13776             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13777                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13778                 if(onMove->pause) {           // thinking engine can be paused
13779                     PauseEngine(onMove);      // do it
13780                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13781                         PauseEngine(onMove->other);
13782                     else
13783                         SendToProgram("easy\n", onMove->other);
13784                     StopClocks();
13785                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13786             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13787                 if(first.pause) {
13788                     PauseEngine(&first);
13789                     StopClocks();
13790                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13791             } else { // human on move, pause pondering by either method
13792                 if(first.pause)
13793                     PauseEngine(&first);
13794                 else if(appData.ponderNextMove)
13795                     SendToProgram("easy\n", &first);
13796                 StopClocks();
13797             }
13798             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13799           case AnalyzeMode:
13800             pausing = TRUE;
13801             ModeHighlight();
13802             break;
13803         }
13804     }
13805 }
13806
13807 void
13808 EditCommentEvent ()
13809 {
13810     char title[MSG_SIZ];
13811
13812     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13813       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13814     } else {
13815       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13816                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13817                parseList[currentMove - 1]);
13818     }
13819
13820     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13821 }
13822
13823
13824 void
13825 EditTagsEvent ()
13826 {
13827     char *tags = PGNTags(&gameInfo);
13828     bookUp = FALSE;
13829     EditTagsPopUp(tags, NULL);
13830     free(tags);
13831 }
13832
13833 void
13834 ToggleSecond ()
13835 {
13836   if(second.analyzing) {
13837     SendToProgram("exit\n", &second);
13838     second.analyzing = FALSE;
13839   } else {
13840     if (second.pr == NoProc) StartChessProgram(&second);
13841     InitChessProgram(&second, FALSE);
13842     FeedMovesToProgram(&second, currentMove);
13843
13844     SendToProgram("analyze\n", &second);
13845     second.analyzing = TRUE;
13846   }
13847 }
13848
13849 /* Toggle ShowThinking */
13850 void
13851 ToggleShowThinking()
13852 {
13853   appData.showThinking = !appData.showThinking;
13854   ShowThinkingEvent();
13855 }
13856
13857 int
13858 AnalyzeModeEvent ()
13859 {
13860     char buf[MSG_SIZ];
13861
13862     if (!first.analysisSupport) {
13863       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13864       DisplayError(buf, 0);
13865       return 0;
13866     }
13867     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13868     if (appData.icsActive) {
13869         if (gameMode != IcsObserving) {
13870           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13871             DisplayError(buf, 0);
13872             /* secure check */
13873             if (appData.icsEngineAnalyze) {
13874                 if (appData.debugMode)
13875                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13876                 ExitAnalyzeMode();
13877                 ModeHighlight();
13878             }
13879             return 0;
13880         }
13881         /* if enable, user wants to disable icsEngineAnalyze */
13882         if (appData.icsEngineAnalyze) {
13883                 ExitAnalyzeMode();
13884                 ModeHighlight();
13885                 return 0;
13886         }
13887         appData.icsEngineAnalyze = TRUE;
13888         if (appData.debugMode)
13889             fprintf(debugFP, "ICS engine analyze starting... \n");
13890     }
13891
13892     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13893     if (appData.noChessProgram || gameMode == AnalyzeMode)
13894       return 0;
13895
13896     if (gameMode != AnalyzeFile) {
13897         if (!appData.icsEngineAnalyze) {
13898                EditGameEvent();
13899                if (gameMode != EditGame) return 0;
13900         }
13901         if (!appData.showThinking) ToggleShowThinking();
13902         ResurrectChessProgram();
13903         SendToProgram("analyze\n", &first);
13904         first.analyzing = TRUE;
13905         /*first.maybeThinking = TRUE;*/
13906         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13907         EngineOutputPopUp();
13908     }
13909     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13910     pausing = FALSE;
13911     ModeHighlight();
13912     SetGameInfo();
13913
13914     StartAnalysisClock();
13915     GetTimeMark(&lastNodeCountTime);
13916     lastNodeCount = 0;
13917     return 1;
13918 }
13919
13920 void
13921 AnalyzeFileEvent ()
13922 {
13923     if (appData.noChessProgram || gameMode == AnalyzeFile)
13924       return;
13925
13926     if (!first.analysisSupport) {
13927       char buf[MSG_SIZ];
13928       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13929       DisplayError(buf, 0);
13930       return;
13931     }
13932
13933     if (gameMode != AnalyzeMode) {
13934         keepInfo = 1; // mere annotating should not alter PGN tags
13935         EditGameEvent();
13936         keepInfo = 0;
13937         if (gameMode != EditGame) return;
13938         if (!appData.showThinking) ToggleShowThinking();
13939         ResurrectChessProgram();
13940         SendToProgram("analyze\n", &first);
13941         first.analyzing = TRUE;
13942         /*first.maybeThinking = TRUE;*/
13943         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13944         EngineOutputPopUp();
13945     }
13946     gameMode = AnalyzeFile;
13947     pausing = FALSE;
13948     ModeHighlight();
13949
13950     StartAnalysisClock();
13951     GetTimeMark(&lastNodeCountTime);
13952     lastNodeCount = 0;
13953     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13954     AnalysisPeriodicEvent(1);
13955 }
13956
13957 void
13958 MachineWhiteEvent ()
13959 {
13960     char buf[MSG_SIZ];
13961     char *bookHit = NULL;
13962
13963     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13964       return;
13965
13966
13967     if (gameMode == PlayFromGameFile ||
13968         gameMode == TwoMachinesPlay  ||
13969         gameMode == Training         ||
13970         gameMode == AnalyzeMode      ||
13971         gameMode == EndOfGame)
13972         EditGameEvent();
13973
13974     if (gameMode == EditPosition)
13975         EditPositionDone(TRUE);
13976
13977     if (!WhiteOnMove(currentMove)) {
13978         DisplayError(_("It is not White's turn"), 0);
13979         return;
13980     }
13981
13982     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13983       ExitAnalyzeMode();
13984
13985     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13986         gameMode == AnalyzeFile)
13987         TruncateGame();
13988
13989     ResurrectChessProgram();    /* in case it isn't running */
13990     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13991         gameMode = MachinePlaysWhite;
13992         ResetClocks();
13993     } else
13994     gameMode = MachinePlaysWhite;
13995     pausing = FALSE;
13996     ModeHighlight();
13997     SetGameInfo();
13998     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13999     DisplayTitle(buf);
14000     if (first.sendName) {
14001       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14002       SendToProgram(buf, &first);
14003     }
14004     if (first.sendTime) {
14005       if (first.useColors) {
14006         SendToProgram("black\n", &first); /*gnu kludge*/
14007       }
14008       SendTimeRemaining(&first, TRUE);
14009     }
14010     if (first.useColors) {
14011       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14012     }
14013     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14014     SetMachineThinkingEnables();
14015     first.maybeThinking = TRUE;
14016     StartClocks();
14017     firstMove = FALSE;
14018
14019     if (appData.autoFlipView && !flipView) {
14020       flipView = !flipView;
14021       DrawPosition(FALSE, NULL);
14022       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14023     }
14024
14025     if(bookHit) { // [HGM] book: simulate book reply
14026         static char bookMove[MSG_SIZ]; // a bit generous?
14027
14028         programStats.nodes = programStats.depth = programStats.time =
14029         programStats.score = programStats.got_only_move = 0;
14030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14031
14032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14033         strcat(bookMove, bookHit);
14034         HandleMachineMove(bookMove, &first);
14035     }
14036 }
14037
14038 void
14039 MachineBlackEvent ()
14040 {
14041   char buf[MSG_SIZ];
14042   char *bookHit = NULL;
14043
14044     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14045         return;
14046
14047
14048     if (gameMode == PlayFromGameFile ||
14049         gameMode == TwoMachinesPlay  ||
14050         gameMode == Training         ||
14051         gameMode == AnalyzeMode      ||
14052         gameMode == EndOfGame)
14053         EditGameEvent();
14054
14055     if (gameMode == EditPosition)
14056         EditPositionDone(TRUE);
14057
14058     if (WhiteOnMove(currentMove)) {
14059         DisplayError(_("It is not Black's turn"), 0);
14060         return;
14061     }
14062
14063     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14064       ExitAnalyzeMode();
14065
14066     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14067         gameMode == AnalyzeFile)
14068         TruncateGame();
14069
14070     ResurrectChessProgram();    /* in case it isn't running */
14071     gameMode = MachinePlaysBlack;
14072     pausing = FALSE;
14073     ModeHighlight();
14074     SetGameInfo();
14075     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14076     DisplayTitle(buf);
14077     if (first.sendName) {
14078       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14079       SendToProgram(buf, &first);
14080     }
14081     if (first.sendTime) {
14082       if (first.useColors) {
14083         SendToProgram("white\n", &first); /*gnu kludge*/
14084       }
14085       SendTimeRemaining(&first, FALSE);
14086     }
14087     if (first.useColors) {
14088       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14089     }
14090     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14091     SetMachineThinkingEnables();
14092     first.maybeThinking = TRUE;
14093     StartClocks();
14094
14095     if (appData.autoFlipView && flipView) {
14096       flipView = !flipView;
14097       DrawPosition(FALSE, NULL);
14098       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14099     }
14100     if(bookHit) { // [HGM] book: simulate book reply
14101         static char bookMove[MSG_SIZ]; // a bit generous?
14102
14103         programStats.nodes = programStats.depth = programStats.time =
14104         programStats.score = programStats.got_only_move = 0;
14105         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14106
14107         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14108         strcat(bookMove, bookHit);
14109         HandleMachineMove(bookMove, &first);
14110     }
14111 }
14112
14113
14114 void
14115 DisplayTwoMachinesTitle ()
14116 {
14117     char buf[MSG_SIZ];
14118     if (appData.matchGames > 0) {
14119         if(appData.tourneyFile[0]) {
14120           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14121                    gameInfo.white, _("vs."), gameInfo.black,
14122                    nextGame+1, appData.matchGames+1,
14123                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14124         } else
14125         if (first.twoMachinesColor[0] == 'w') {
14126           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14127                    gameInfo.white, _("vs."),  gameInfo.black,
14128                    first.matchWins, second.matchWins,
14129                    matchGame - 1 - (first.matchWins + second.matchWins));
14130         } else {
14131           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14132                    gameInfo.white, _("vs."), gameInfo.black,
14133                    second.matchWins, first.matchWins,
14134                    matchGame - 1 - (first.matchWins + second.matchWins));
14135         }
14136     } else {
14137       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14138     }
14139     DisplayTitle(buf);
14140 }
14141
14142 void
14143 SettingsMenuIfReady ()
14144 {
14145   if (second.lastPing != second.lastPong) {
14146     DisplayMessage("", _("Waiting for second chess program"));
14147     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14148     return;
14149   }
14150   ThawUI();
14151   DisplayMessage("", "");
14152   SettingsPopUp(&second);
14153 }
14154
14155 int
14156 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14157 {
14158     char buf[MSG_SIZ];
14159     if (cps->pr == NoProc) {
14160         StartChessProgram(cps);
14161         if (cps->protocolVersion == 1) {
14162           retry();
14163           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14164         } else {
14165           /* kludge: allow timeout for initial "feature" command */
14166           if(retry != TwoMachinesEventIfReady) FreezeUI();
14167           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14168           DisplayMessage("", buf);
14169           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14170         }
14171         return 1;
14172     }
14173     return 0;
14174 }
14175
14176 void
14177 TwoMachinesEvent P((void))
14178 {
14179     int i;
14180     char buf[MSG_SIZ];
14181     ChessProgramState *onmove;
14182     char *bookHit = NULL;
14183     static int stalling = 0;
14184     TimeMark now;
14185     long wait;
14186
14187     if (appData.noChessProgram) return;
14188
14189     switch (gameMode) {
14190       case TwoMachinesPlay:
14191         return;
14192       case MachinePlaysWhite:
14193       case MachinePlaysBlack:
14194         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14195             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14196             return;
14197         }
14198         /* fall through */
14199       case BeginningOfGame:
14200       case PlayFromGameFile:
14201       case EndOfGame:
14202         EditGameEvent();
14203         if (gameMode != EditGame) return;
14204         break;
14205       case EditPosition:
14206         EditPositionDone(TRUE);
14207         break;
14208       case AnalyzeMode:
14209       case AnalyzeFile:
14210         ExitAnalyzeMode();
14211         break;
14212       case EditGame:
14213       default:
14214         break;
14215     }
14216
14217 //    forwardMostMove = currentMove;
14218     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14219     startingEngine = TRUE;
14220
14221     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14222
14223     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14224     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14225       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14226       return;
14227     }
14228     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14229
14230     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14231         startingEngine = FALSE;
14232         DisplayError("second engine does not play this", 0);
14233         return;
14234     }
14235
14236     if(!stalling) {
14237       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14238       SendToProgram("force\n", &second);
14239       stalling = 1;
14240       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14241       return;
14242     }
14243     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14244     if(appData.matchPause>10000 || appData.matchPause<10)
14245                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14246     wait = SubtractTimeMarks(&now, &pauseStart);
14247     if(wait < appData.matchPause) {
14248         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14249         return;
14250     }
14251     // we are now committed to starting the game
14252     stalling = 0;
14253     DisplayMessage("", "");
14254     if (startedFromSetupPosition) {
14255         SendBoard(&second, backwardMostMove);
14256     if (appData.debugMode) {
14257         fprintf(debugFP, "Two Machines\n");
14258     }
14259     }
14260     for (i = backwardMostMove; i < forwardMostMove; i++) {
14261         SendMoveToProgram(i, &second);
14262     }
14263
14264     gameMode = TwoMachinesPlay;
14265     pausing = startingEngine = FALSE;
14266     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14267     SetGameInfo();
14268     DisplayTwoMachinesTitle();
14269     firstMove = TRUE;
14270     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14271         onmove = &first;
14272     } else {
14273         onmove = &second;
14274     }
14275     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14276     SendToProgram(first.computerString, &first);
14277     if (first.sendName) {
14278       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14279       SendToProgram(buf, &first);
14280     }
14281     SendToProgram(second.computerString, &second);
14282     if (second.sendName) {
14283       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14284       SendToProgram(buf, &second);
14285     }
14286
14287     ResetClocks();
14288     if (!first.sendTime || !second.sendTime) {
14289         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14290         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14291     }
14292     if (onmove->sendTime) {
14293       if (onmove->useColors) {
14294         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14295       }
14296       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14297     }
14298     if (onmove->useColors) {
14299       SendToProgram(onmove->twoMachinesColor, onmove);
14300     }
14301     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14302 //    SendToProgram("go\n", onmove);
14303     onmove->maybeThinking = TRUE;
14304     SetMachineThinkingEnables();
14305
14306     StartClocks();
14307
14308     if(bookHit) { // [HGM] book: simulate book reply
14309         static char bookMove[MSG_SIZ]; // a bit generous?
14310
14311         programStats.nodes = programStats.depth = programStats.time =
14312         programStats.score = programStats.got_only_move = 0;
14313         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14314
14315         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14316         strcat(bookMove, bookHit);
14317         savedMessage = bookMove; // args for deferred call
14318         savedState = onmove;
14319         ScheduleDelayedEvent(DeferredBookMove, 1);
14320     }
14321 }
14322
14323 void
14324 TrainingEvent ()
14325 {
14326     if (gameMode == Training) {
14327       SetTrainingModeOff();
14328       gameMode = PlayFromGameFile;
14329       DisplayMessage("", _("Training mode off"));
14330     } else {
14331       gameMode = Training;
14332       animateTraining = appData.animate;
14333
14334       /* make sure we are not already at the end of the game */
14335       if (currentMove < forwardMostMove) {
14336         SetTrainingModeOn();
14337         DisplayMessage("", _("Training mode on"));
14338       } else {
14339         gameMode = PlayFromGameFile;
14340         DisplayError(_("Already at end of game"), 0);
14341       }
14342     }
14343     ModeHighlight();
14344 }
14345
14346 void
14347 IcsClientEvent ()
14348 {
14349     if (!appData.icsActive) return;
14350     switch (gameMode) {
14351       case IcsPlayingWhite:
14352       case IcsPlayingBlack:
14353       case IcsObserving:
14354       case IcsIdle:
14355       case BeginningOfGame:
14356       case IcsExamining:
14357         return;
14358
14359       case EditGame:
14360         break;
14361
14362       case EditPosition:
14363         EditPositionDone(TRUE);
14364         break;
14365
14366       case AnalyzeMode:
14367       case AnalyzeFile:
14368         ExitAnalyzeMode();
14369         break;
14370
14371       default:
14372         EditGameEvent();
14373         break;
14374     }
14375
14376     gameMode = IcsIdle;
14377     ModeHighlight();
14378     return;
14379 }
14380
14381 void
14382 EditGameEvent ()
14383 {
14384     int i;
14385
14386     switch (gameMode) {
14387       case Training:
14388         SetTrainingModeOff();
14389         break;
14390       case MachinePlaysWhite:
14391       case MachinePlaysBlack:
14392       case BeginningOfGame:
14393         SendToProgram("force\n", &first);
14394         SetUserThinkingEnables();
14395         break;
14396       case PlayFromGameFile:
14397         (void) StopLoadGameTimer();
14398         if (gameFileFP != NULL) {
14399             gameFileFP = NULL;
14400         }
14401         break;
14402       case EditPosition:
14403         EditPositionDone(TRUE);
14404         break;
14405       case AnalyzeMode:
14406       case AnalyzeFile:
14407         ExitAnalyzeMode();
14408         SendToProgram("force\n", &first);
14409         break;
14410       case TwoMachinesPlay:
14411         GameEnds(EndOfFile, NULL, GE_PLAYER);
14412         ResurrectChessProgram();
14413         SetUserThinkingEnables();
14414         break;
14415       case EndOfGame:
14416         ResurrectChessProgram();
14417         break;
14418       case IcsPlayingBlack:
14419       case IcsPlayingWhite:
14420         DisplayError(_("Warning: You are still playing a game"), 0);
14421         break;
14422       case IcsObserving:
14423         DisplayError(_("Warning: You are still observing a game"), 0);
14424         break;
14425       case IcsExamining:
14426         DisplayError(_("Warning: You are still examining a game"), 0);
14427         break;
14428       case IcsIdle:
14429         break;
14430       case EditGame:
14431       default:
14432         return;
14433     }
14434
14435     pausing = FALSE;
14436     StopClocks();
14437     first.offeredDraw = second.offeredDraw = 0;
14438
14439     if (gameMode == PlayFromGameFile) {
14440         whiteTimeRemaining = timeRemaining[0][currentMove];
14441         blackTimeRemaining = timeRemaining[1][currentMove];
14442         DisplayTitle("");
14443     }
14444
14445     if (gameMode == MachinePlaysWhite ||
14446         gameMode == MachinePlaysBlack ||
14447         gameMode == TwoMachinesPlay ||
14448         gameMode == EndOfGame) {
14449         i = forwardMostMove;
14450         while (i > currentMove) {
14451             SendToProgram("undo\n", &first);
14452             i--;
14453         }
14454         if(!adjustedClock) {
14455         whiteTimeRemaining = timeRemaining[0][currentMove];
14456         blackTimeRemaining = timeRemaining[1][currentMove];
14457         DisplayBothClocks();
14458         }
14459         if (whiteFlag || blackFlag) {
14460             whiteFlag = blackFlag = 0;
14461         }
14462         DisplayTitle("");
14463     }
14464
14465     gameMode = EditGame;
14466     ModeHighlight();
14467     SetGameInfo();
14468 }
14469
14470
14471 void
14472 EditPositionEvent ()
14473 {
14474     if (gameMode == EditPosition) {
14475         EditGameEvent();
14476         return;
14477     }
14478
14479     EditGameEvent();
14480     if (gameMode != EditGame) return;
14481
14482     gameMode = EditPosition;
14483     ModeHighlight();
14484     SetGameInfo();
14485     if (currentMove > 0)
14486       CopyBoard(boards[0], boards[currentMove]);
14487
14488     blackPlaysFirst = !WhiteOnMove(currentMove);
14489     ResetClocks();
14490     currentMove = forwardMostMove = backwardMostMove = 0;
14491     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14492     DisplayMove(-1);
14493     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14494 }
14495
14496 void
14497 ExitAnalyzeMode ()
14498 {
14499     /* [DM] icsEngineAnalyze - possible call from other functions */
14500     if (appData.icsEngineAnalyze) {
14501         appData.icsEngineAnalyze = FALSE;
14502
14503         DisplayMessage("",_("Close ICS engine analyze..."));
14504     }
14505     if (first.analysisSupport && first.analyzing) {
14506       SendToBoth("exit\n");
14507       first.analyzing = second.analyzing = FALSE;
14508     }
14509     thinkOutput[0] = NULLCHAR;
14510 }
14511
14512 void
14513 EditPositionDone (Boolean fakeRights)
14514 {
14515     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14516
14517     startedFromSetupPosition = TRUE;
14518     InitChessProgram(&first, FALSE);
14519     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14520       boards[0][EP_STATUS] = EP_NONE;
14521       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14522       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14523         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14524         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14525       } else boards[0][CASTLING][2] = NoRights;
14526       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14527         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14528         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14529       } else boards[0][CASTLING][5] = NoRights;
14530       if(gameInfo.variant == VariantSChess) {
14531         int i;
14532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14533           boards[0][VIRGIN][i] = 0;
14534           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14535           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14536         }
14537       }
14538     }
14539     SendToProgram("force\n", &first);
14540     if (blackPlaysFirst) {
14541         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14542         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14543         currentMove = forwardMostMove = backwardMostMove = 1;
14544         CopyBoard(boards[1], boards[0]);
14545     } else {
14546         currentMove = forwardMostMove = backwardMostMove = 0;
14547     }
14548     SendBoard(&first, forwardMostMove);
14549     if (appData.debugMode) {
14550         fprintf(debugFP, "EditPosDone\n");
14551     }
14552     DisplayTitle("");
14553     DisplayMessage("", "");
14554     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14555     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14556     gameMode = EditGame;
14557     ModeHighlight();
14558     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14559     ClearHighlights(); /* [AS] */
14560 }
14561
14562 /* Pause for `ms' milliseconds */
14563 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14564 void
14565 TimeDelay (long ms)
14566 {
14567     TimeMark m1, m2;
14568
14569     GetTimeMark(&m1);
14570     do {
14571         GetTimeMark(&m2);
14572     } while (SubtractTimeMarks(&m2, &m1) < ms);
14573 }
14574
14575 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14576 void
14577 SendMultiLineToICS (char *buf)
14578 {
14579     char temp[MSG_SIZ+1], *p;
14580     int len;
14581
14582     len = strlen(buf);
14583     if (len > MSG_SIZ)
14584       len = MSG_SIZ;
14585
14586     strncpy(temp, buf, len);
14587     temp[len] = 0;
14588
14589     p = temp;
14590     while (*p) {
14591         if (*p == '\n' || *p == '\r')
14592           *p = ' ';
14593         ++p;
14594     }
14595
14596     strcat(temp, "\n");
14597     SendToICS(temp);
14598     SendToPlayer(temp, strlen(temp));
14599 }
14600
14601 void
14602 SetWhiteToPlayEvent ()
14603 {
14604     if (gameMode == EditPosition) {
14605         blackPlaysFirst = FALSE;
14606         DisplayBothClocks();    /* works because currentMove is 0 */
14607     } else if (gameMode == IcsExamining) {
14608         SendToICS(ics_prefix);
14609         SendToICS("tomove white\n");
14610     }
14611 }
14612
14613 void
14614 SetBlackToPlayEvent ()
14615 {
14616     if (gameMode == EditPosition) {
14617         blackPlaysFirst = TRUE;
14618         currentMove = 1;        /* kludge */
14619         DisplayBothClocks();
14620         currentMove = 0;
14621     } else if (gameMode == IcsExamining) {
14622         SendToICS(ics_prefix);
14623         SendToICS("tomove black\n");
14624     }
14625 }
14626
14627 void
14628 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14629 {
14630     char buf[MSG_SIZ];
14631     ChessSquare piece = boards[0][y][x];
14632     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14633     static int lastVariant;
14634
14635     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14636
14637     switch (selection) {
14638       case ClearBoard:
14639         CopyBoard(currentBoard, boards[0]);
14640         CopyBoard(menuBoard, initialPosition);
14641         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14642             SendToICS(ics_prefix);
14643             SendToICS("bsetup clear\n");
14644         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14645             SendToICS(ics_prefix);
14646             SendToICS("clearboard\n");
14647         } else {
14648             int nonEmpty = 0;
14649             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14650                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14651                 for (y = 0; y < BOARD_HEIGHT; y++) {
14652                     if (gameMode == IcsExamining) {
14653                         if (boards[currentMove][y][x] != EmptySquare) {
14654                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14655                                     AAA + x, ONE + y);
14656                             SendToICS(buf);
14657                         }
14658                     } else {
14659                         if(boards[0][y][x] != p) nonEmpty++;
14660                         boards[0][y][x] = p;
14661                     }
14662                 }
14663                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14664             }
14665             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14666                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14667                     ChessSquare p = menuBoard[0][x];
14668                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14669                     p = menuBoard[BOARD_HEIGHT-1][x];
14670                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14671                 }
14672                 DisplayMessage("Clicking clock again restores position", "");
14673                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14674                 if(!nonEmpty) { // asked to clear an empty board
14675                     CopyBoard(boards[0], menuBoard);
14676                 } else
14677                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14678                     CopyBoard(boards[0], initialPosition);
14679                 } else
14680                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14681                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14682                     CopyBoard(boards[0], erasedBoard);
14683                 } else
14684                     CopyBoard(erasedBoard, currentBoard);
14685
14686             }
14687         }
14688         if (gameMode == EditPosition) {
14689             DrawPosition(FALSE, boards[0]);
14690         }
14691         break;
14692
14693       case WhitePlay:
14694         SetWhiteToPlayEvent();
14695         break;
14696
14697       case BlackPlay:
14698         SetBlackToPlayEvent();
14699         break;
14700
14701       case EmptySquare:
14702         if (gameMode == IcsExamining) {
14703             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14704             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14705             SendToICS(buf);
14706         } else {
14707             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14708                 if(x == BOARD_LEFT-2) {
14709                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14710                     boards[0][y][1] = 0;
14711                 } else
14712                 if(x == BOARD_RGHT+1) {
14713                     if(y >= gameInfo.holdingsSize) break;
14714                     boards[0][y][BOARD_WIDTH-2] = 0;
14715                 } else break;
14716             }
14717             boards[0][y][x] = EmptySquare;
14718             DrawPosition(FALSE, boards[0]);
14719         }
14720         break;
14721
14722       case PromotePiece:
14723         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14724            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14725             selection = (ChessSquare) (PROMOTED piece);
14726         } else if(piece == EmptySquare) selection = WhiteSilver;
14727         else selection = (ChessSquare)((int)piece - 1);
14728         goto defaultlabel;
14729
14730       case DemotePiece:
14731         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14732            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14733             selection = (ChessSquare) (DEMOTED piece);
14734         } else if(piece == EmptySquare) selection = BlackSilver;
14735         else selection = (ChessSquare)((int)piece + 1);
14736         goto defaultlabel;
14737
14738       case WhiteQueen:
14739       case BlackQueen:
14740         if(gameInfo.variant == VariantShatranj ||
14741            gameInfo.variant == VariantXiangqi  ||
14742            gameInfo.variant == VariantCourier  ||
14743            gameInfo.variant == VariantASEAN    ||
14744            gameInfo.variant == VariantMakruk     )
14745             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14746         goto defaultlabel;
14747
14748       case WhiteKing:
14749       case BlackKing:
14750         if(gameInfo.variant == VariantXiangqi)
14751             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14752         if(gameInfo.variant == VariantKnightmate)
14753             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14754       default:
14755         defaultlabel:
14756         if (gameMode == IcsExamining) {
14757             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14758             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14759                      PieceToChar(selection), AAA + x, ONE + y);
14760             SendToICS(buf);
14761         } else {
14762             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14763                 int n;
14764                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14765                     n = PieceToNumber(selection - BlackPawn);
14766                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14767                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14768                     boards[0][BOARD_HEIGHT-1-n][1]++;
14769                 } else
14770                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14771                     n = PieceToNumber(selection);
14772                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14773                     boards[0][n][BOARD_WIDTH-1] = selection;
14774                     boards[0][n][BOARD_WIDTH-2]++;
14775                 }
14776             } else
14777             boards[0][y][x] = selection;
14778             DrawPosition(TRUE, boards[0]);
14779             ClearHighlights();
14780             fromX = fromY = -1;
14781         }
14782         break;
14783     }
14784 }
14785
14786
14787 void
14788 DropMenuEvent (ChessSquare selection, int x, int y)
14789 {
14790     ChessMove moveType;
14791
14792     switch (gameMode) {
14793       case IcsPlayingWhite:
14794       case MachinePlaysBlack:
14795         if (!WhiteOnMove(currentMove)) {
14796             DisplayMoveError(_("It is Black's turn"));
14797             return;
14798         }
14799         moveType = WhiteDrop;
14800         break;
14801       case IcsPlayingBlack:
14802       case MachinePlaysWhite:
14803         if (WhiteOnMove(currentMove)) {
14804             DisplayMoveError(_("It is White's turn"));
14805             return;
14806         }
14807         moveType = BlackDrop;
14808         break;
14809       case EditGame:
14810         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14811         break;
14812       default:
14813         return;
14814     }
14815
14816     if (moveType == BlackDrop && selection < BlackPawn) {
14817       selection = (ChessSquare) ((int) selection
14818                                  + (int) BlackPawn - (int) WhitePawn);
14819     }
14820     if (boards[currentMove][y][x] != EmptySquare) {
14821         DisplayMoveError(_("That square is occupied"));
14822         return;
14823     }
14824
14825     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14826 }
14827
14828 void
14829 AcceptEvent ()
14830 {
14831     /* Accept a pending offer of any kind from opponent */
14832
14833     if (appData.icsActive) {
14834         SendToICS(ics_prefix);
14835         SendToICS("accept\n");
14836     } else if (cmailMsgLoaded) {
14837         if (currentMove == cmailOldMove &&
14838             commentList[cmailOldMove] != NULL &&
14839             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14840                    "Black offers a draw" : "White offers a draw")) {
14841             TruncateGame();
14842             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14843             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14844         } else {
14845             DisplayError(_("There is no pending offer on this move"), 0);
14846             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14847         }
14848     } else {
14849         /* Not used for offers from chess program */
14850     }
14851 }
14852
14853 void
14854 DeclineEvent ()
14855 {
14856     /* Decline a pending offer of any kind from opponent */
14857
14858     if (appData.icsActive) {
14859         SendToICS(ics_prefix);
14860         SendToICS("decline\n");
14861     } else if (cmailMsgLoaded) {
14862         if (currentMove == cmailOldMove &&
14863             commentList[cmailOldMove] != NULL &&
14864             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14865                    "Black offers a draw" : "White offers a draw")) {
14866 #ifdef NOTDEF
14867             AppendComment(cmailOldMove, "Draw declined", TRUE);
14868             DisplayComment(cmailOldMove - 1, "Draw declined");
14869 #endif /*NOTDEF*/
14870         } else {
14871             DisplayError(_("There is no pending offer on this move"), 0);
14872         }
14873     } else {
14874         /* Not used for offers from chess program */
14875     }
14876 }
14877
14878 void
14879 RematchEvent ()
14880 {
14881     /* Issue ICS rematch command */
14882     if (appData.icsActive) {
14883         SendToICS(ics_prefix);
14884         SendToICS("rematch\n");
14885     }
14886 }
14887
14888 void
14889 CallFlagEvent ()
14890 {
14891     /* Call your opponent's flag (claim a win on time) */
14892     if (appData.icsActive) {
14893         SendToICS(ics_prefix);
14894         SendToICS("flag\n");
14895     } else {
14896         switch (gameMode) {
14897           default:
14898             return;
14899           case MachinePlaysWhite:
14900             if (whiteFlag) {
14901                 if (blackFlag)
14902                   GameEnds(GameIsDrawn, "Both players ran out of time",
14903                            GE_PLAYER);
14904                 else
14905                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14906             } else {
14907                 DisplayError(_("Your opponent is not out of time"), 0);
14908             }
14909             break;
14910           case MachinePlaysBlack:
14911             if (blackFlag) {
14912                 if (whiteFlag)
14913                   GameEnds(GameIsDrawn, "Both players ran out of time",
14914                            GE_PLAYER);
14915                 else
14916                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14917             } else {
14918                 DisplayError(_("Your opponent is not out of time"), 0);
14919             }
14920             break;
14921         }
14922     }
14923 }
14924
14925 void
14926 ClockClick (int which)
14927 {       // [HGM] code moved to back-end from winboard.c
14928         if(which) { // black clock
14929           if (gameMode == EditPosition || gameMode == IcsExamining) {
14930             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14931             SetBlackToPlayEvent();
14932           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14933           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14934           } else if (shiftKey) {
14935             AdjustClock(which, -1);
14936           } else if (gameMode == IcsPlayingWhite ||
14937                      gameMode == MachinePlaysBlack) {
14938             CallFlagEvent();
14939           }
14940         } else { // white clock
14941           if (gameMode == EditPosition || gameMode == IcsExamining) {
14942             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14943             SetWhiteToPlayEvent();
14944           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14945           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14946           } else if (shiftKey) {
14947             AdjustClock(which, -1);
14948           } else if (gameMode == IcsPlayingBlack ||
14949                    gameMode == MachinePlaysWhite) {
14950             CallFlagEvent();
14951           }
14952         }
14953 }
14954
14955 void
14956 DrawEvent ()
14957 {
14958     /* Offer draw or accept pending draw offer from opponent */
14959
14960     if (appData.icsActive) {
14961         /* Note: tournament rules require draw offers to be
14962            made after you make your move but before you punch
14963            your clock.  Currently ICS doesn't let you do that;
14964            instead, you immediately punch your clock after making
14965            a move, but you can offer a draw at any time. */
14966
14967         SendToICS(ics_prefix);
14968         SendToICS("draw\n");
14969         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14970     } else if (cmailMsgLoaded) {
14971         if (currentMove == cmailOldMove &&
14972             commentList[cmailOldMove] != NULL &&
14973             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14974                    "Black offers a draw" : "White offers a draw")) {
14975             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14976             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14977         } else if (currentMove == cmailOldMove + 1) {
14978             char *offer = WhiteOnMove(cmailOldMove) ?
14979               "White offers a draw" : "Black offers a draw";
14980             AppendComment(currentMove, offer, TRUE);
14981             DisplayComment(currentMove - 1, offer);
14982             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14983         } else {
14984             DisplayError(_("You must make your move before offering a draw"), 0);
14985             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14986         }
14987     } else if (first.offeredDraw) {
14988         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14989     } else {
14990         if (first.sendDrawOffers) {
14991             SendToProgram("draw\n", &first);
14992             userOfferedDraw = TRUE;
14993         }
14994     }
14995 }
14996
14997 void
14998 AdjournEvent ()
14999 {
15000     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15001
15002     if (appData.icsActive) {
15003         SendToICS(ics_prefix);
15004         SendToICS("adjourn\n");
15005     } else {
15006         /* Currently GNU Chess doesn't offer or accept Adjourns */
15007     }
15008 }
15009
15010
15011 void
15012 AbortEvent ()
15013 {
15014     /* Offer Abort or accept pending Abort offer from opponent */
15015
15016     if (appData.icsActive) {
15017         SendToICS(ics_prefix);
15018         SendToICS("abort\n");
15019     } else {
15020         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15021     }
15022 }
15023
15024 void
15025 ResignEvent ()
15026 {
15027     /* Resign.  You can do this even if it's not your turn. */
15028
15029     if (appData.icsActive) {
15030         SendToICS(ics_prefix);
15031         SendToICS("resign\n");
15032     } else {
15033         switch (gameMode) {
15034           case MachinePlaysWhite:
15035             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15036             break;
15037           case MachinePlaysBlack:
15038             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15039             break;
15040           case EditGame:
15041             if (cmailMsgLoaded) {
15042                 TruncateGame();
15043                 if (WhiteOnMove(cmailOldMove)) {
15044                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15045                 } else {
15046                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15047                 }
15048                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15049             }
15050             break;
15051           default:
15052             break;
15053         }
15054     }
15055 }
15056
15057
15058 void
15059 StopObservingEvent ()
15060 {
15061     /* Stop observing current games */
15062     SendToICS(ics_prefix);
15063     SendToICS("unobserve\n");
15064 }
15065
15066 void
15067 StopExaminingEvent ()
15068 {
15069     /* Stop observing current game */
15070     SendToICS(ics_prefix);
15071     SendToICS("unexamine\n");
15072 }
15073
15074 void
15075 ForwardInner (int target)
15076 {
15077     int limit; int oldSeekGraphUp = seekGraphUp;
15078
15079     if (appData.debugMode)
15080         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15081                 target, currentMove, forwardMostMove);
15082
15083     if (gameMode == EditPosition)
15084       return;
15085
15086     seekGraphUp = FALSE;
15087     MarkTargetSquares(1);
15088
15089     if (gameMode == PlayFromGameFile && !pausing)
15090       PauseEvent();
15091
15092     if (gameMode == IcsExamining && pausing)
15093       limit = pauseExamForwardMostMove;
15094     else
15095       limit = forwardMostMove;
15096
15097     if (target > limit) target = limit;
15098
15099     if (target > 0 && moveList[target - 1][0]) {
15100         int fromX, fromY, toX, toY;
15101         toX = moveList[target - 1][2] - AAA;
15102         toY = moveList[target - 1][3] - ONE;
15103         if (moveList[target - 1][1] == '@') {
15104             if (appData.highlightLastMove) {
15105                 SetHighlights(-1, -1, toX, toY);
15106             }
15107         } else {
15108             fromX = moveList[target - 1][0] - AAA;
15109             fromY = moveList[target - 1][1] - ONE;
15110             if (target == currentMove + 1) {
15111                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15112             }
15113             if (appData.highlightLastMove) {
15114                 SetHighlights(fromX, fromY, toX, toY);
15115             }
15116         }
15117     }
15118     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15119         gameMode == Training || gameMode == PlayFromGameFile ||
15120         gameMode == AnalyzeFile) {
15121         while (currentMove < target) {
15122             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15123             SendMoveToProgram(currentMove++, &first);
15124         }
15125     } else {
15126         currentMove = target;
15127     }
15128
15129     if (gameMode == EditGame || gameMode == EndOfGame) {
15130         whiteTimeRemaining = timeRemaining[0][currentMove];
15131         blackTimeRemaining = timeRemaining[1][currentMove];
15132     }
15133     DisplayBothClocks();
15134     DisplayMove(currentMove - 1);
15135     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15136     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15137     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15138         DisplayComment(currentMove - 1, commentList[currentMove]);
15139     }
15140     ClearMap(); // [HGM] exclude: invalidate map
15141 }
15142
15143
15144 void
15145 ForwardEvent ()
15146 {
15147     if (gameMode == IcsExamining && !pausing) {
15148         SendToICS(ics_prefix);
15149         SendToICS("forward\n");
15150     } else {
15151         ForwardInner(currentMove + 1);
15152     }
15153 }
15154
15155 void
15156 ToEndEvent ()
15157 {
15158     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15159         /* to optimze, we temporarily turn off analysis mode while we feed
15160          * the remaining moves to the engine. Otherwise we get analysis output
15161          * after each move.
15162          */
15163         if (first.analysisSupport) {
15164           SendToProgram("exit\nforce\n", &first);
15165           first.analyzing = FALSE;
15166         }
15167     }
15168
15169     if (gameMode == IcsExamining && !pausing) {
15170         SendToICS(ics_prefix);
15171         SendToICS("forward 999999\n");
15172     } else {
15173         ForwardInner(forwardMostMove);
15174     }
15175
15176     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15177         /* we have fed all the moves, so reactivate analysis mode */
15178         SendToProgram("analyze\n", &first);
15179         first.analyzing = TRUE;
15180         /*first.maybeThinking = TRUE;*/
15181         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15182     }
15183 }
15184
15185 void
15186 BackwardInner (int target)
15187 {
15188     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15189
15190     if (appData.debugMode)
15191         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15192                 target, currentMove, forwardMostMove);
15193
15194     if (gameMode == EditPosition) return;
15195     seekGraphUp = FALSE;
15196     MarkTargetSquares(1);
15197     if (currentMove <= backwardMostMove) {
15198         ClearHighlights();
15199         DrawPosition(full_redraw, boards[currentMove]);
15200         return;
15201     }
15202     if (gameMode == PlayFromGameFile && !pausing)
15203       PauseEvent();
15204
15205     if (moveList[target][0]) {
15206         int fromX, fromY, toX, toY;
15207         toX = moveList[target][2] - AAA;
15208         toY = moveList[target][3] - ONE;
15209         if (moveList[target][1] == '@') {
15210             if (appData.highlightLastMove) {
15211                 SetHighlights(-1, -1, toX, toY);
15212             }
15213         } else {
15214             fromX = moveList[target][0] - AAA;
15215             fromY = moveList[target][1] - ONE;
15216             if (target == currentMove - 1) {
15217                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15218             }
15219             if (appData.highlightLastMove) {
15220                 SetHighlights(fromX, fromY, toX, toY);
15221             }
15222         }
15223     }
15224     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15225         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15226         while (currentMove > target) {
15227             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15228                 // null move cannot be undone. Reload program with move history before it.
15229                 int i;
15230                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15231                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15232                 }
15233                 SendBoard(&first, i);
15234               if(second.analyzing) SendBoard(&second, i);
15235                 for(currentMove=i; currentMove<target; currentMove++) {
15236                     SendMoveToProgram(currentMove, &first);
15237                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15238                 }
15239                 break;
15240             }
15241             SendToBoth("undo\n");
15242             currentMove--;
15243         }
15244     } else {
15245         currentMove = target;
15246     }
15247
15248     if (gameMode == EditGame || gameMode == EndOfGame) {
15249         whiteTimeRemaining = timeRemaining[0][currentMove];
15250         blackTimeRemaining = timeRemaining[1][currentMove];
15251     }
15252     DisplayBothClocks();
15253     DisplayMove(currentMove - 1);
15254     DrawPosition(full_redraw, boards[currentMove]);
15255     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15256     // [HGM] PV info: routine tests if comment empty
15257     DisplayComment(currentMove - 1, commentList[currentMove]);
15258     ClearMap(); // [HGM] exclude: invalidate map
15259 }
15260
15261 void
15262 BackwardEvent ()
15263 {
15264     if (gameMode == IcsExamining && !pausing) {
15265         SendToICS(ics_prefix);
15266         SendToICS("backward\n");
15267     } else {
15268         BackwardInner(currentMove - 1);
15269     }
15270 }
15271
15272 void
15273 ToStartEvent ()
15274 {
15275     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15276         /* to optimize, we temporarily turn off analysis mode while we undo
15277          * all the moves. Otherwise we get analysis output after each undo.
15278          */
15279         if (first.analysisSupport) {
15280           SendToProgram("exit\nforce\n", &first);
15281           first.analyzing = FALSE;
15282         }
15283     }
15284
15285     if (gameMode == IcsExamining && !pausing) {
15286         SendToICS(ics_prefix);
15287         SendToICS("backward 999999\n");
15288     } else {
15289         BackwardInner(backwardMostMove);
15290     }
15291
15292     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15293         /* we have fed all the moves, so reactivate analysis mode */
15294         SendToProgram("analyze\n", &first);
15295         first.analyzing = TRUE;
15296         /*first.maybeThinking = TRUE;*/
15297         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15298     }
15299 }
15300
15301 void
15302 ToNrEvent (int to)
15303 {
15304   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15305   if (to >= forwardMostMove) to = forwardMostMove;
15306   if (to <= backwardMostMove) to = backwardMostMove;
15307   if (to < currentMove) {
15308     BackwardInner(to);
15309   } else {
15310     ForwardInner(to);
15311   }
15312 }
15313
15314 void
15315 RevertEvent (Boolean annotate)
15316 {
15317     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15318         return;
15319     }
15320     if (gameMode != IcsExamining) {
15321         DisplayError(_("You are not examining a game"), 0);
15322         return;
15323     }
15324     if (pausing) {
15325         DisplayError(_("You can't revert while pausing"), 0);
15326         return;
15327     }
15328     SendToICS(ics_prefix);
15329     SendToICS("revert\n");
15330 }
15331
15332 void
15333 RetractMoveEvent ()
15334 {
15335     switch (gameMode) {
15336       case MachinePlaysWhite:
15337       case MachinePlaysBlack:
15338         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15339             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15340             return;
15341         }
15342         if (forwardMostMove < 2) return;
15343         currentMove = forwardMostMove = forwardMostMove - 2;
15344         whiteTimeRemaining = timeRemaining[0][currentMove];
15345         blackTimeRemaining = timeRemaining[1][currentMove];
15346         DisplayBothClocks();
15347         DisplayMove(currentMove - 1);
15348         ClearHighlights();/*!! could figure this out*/
15349         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15350         SendToProgram("remove\n", &first);
15351         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15352         break;
15353
15354       case BeginningOfGame:
15355       default:
15356         break;
15357
15358       case IcsPlayingWhite:
15359       case IcsPlayingBlack:
15360         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15361             SendToICS(ics_prefix);
15362             SendToICS("takeback 2\n");
15363         } else {
15364             SendToICS(ics_prefix);
15365             SendToICS("takeback 1\n");
15366         }
15367         break;
15368     }
15369 }
15370
15371 void
15372 MoveNowEvent ()
15373 {
15374     ChessProgramState *cps;
15375
15376     switch (gameMode) {
15377       case MachinePlaysWhite:
15378         if (!WhiteOnMove(forwardMostMove)) {
15379             DisplayError(_("It is your turn"), 0);
15380             return;
15381         }
15382         cps = &first;
15383         break;
15384       case MachinePlaysBlack:
15385         if (WhiteOnMove(forwardMostMove)) {
15386             DisplayError(_("It is your turn"), 0);
15387             return;
15388         }
15389         cps = &first;
15390         break;
15391       case TwoMachinesPlay:
15392         if (WhiteOnMove(forwardMostMove) ==
15393             (first.twoMachinesColor[0] == 'w')) {
15394             cps = &first;
15395         } else {
15396             cps = &second;
15397         }
15398         break;
15399       case BeginningOfGame:
15400       default:
15401         return;
15402     }
15403     SendToProgram("?\n", cps);
15404 }
15405
15406 void
15407 TruncateGameEvent ()
15408 {
15409     EditGameEvent();
15410     if (gameMode != EditGame) return;
15411     TruncateGame();
15412 }
15413
15414 void
15415 TruncateGame ()
15416 {
15417     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15418     if (forwardMostMove > currentMove) {
15419         if (gameInfo.resultDetails != NULL) {
15420             free(gameInfo.resultDetails);
15421             gameInfo.resultDetails = NULL;
15422             gameInfo.result = GameUnfinished;
15423         }
15424         forwardMostMove = currentMove;
15425         HistorySet(parseList, backwardMostMove, forwardMostMove,
15426                    currentMove-1);
15427     }
15428 }
15429
15430 void
15431 HintEvent ()
15432 {
15433     if (appData.noChessProgram) return;
15434     switch (gameMode) {
15435       case MachinePlaysWhite:
15436         if (WhiteOnMove(forwardMostMove)) {
15437             DisplayError(_("Wait until your turn."), 0);
15438             return;
15439         }
15440         break;
15441       case BeginningOfGame:
15442       case MachinePlaysBlack:
15443         if (!WhiteOnMove(forwardMostMove)) {
15444             DisplayError(_("Wait until your turn."), 0);
15445             return;
15446         }
15447         break;
15448       default:
15449         DisplayError(_("No hint available"), 0);
15450         return;
15451     }
15452     SendToProgram("hint\n", &first);
15453     hintRequested = TRUE;
15454 }
15455
15456 void
15457 CreateBookEvent ()
15458 {
15459     ListGame * lg = (ListGame *) gameList.head;
15460     FILE *f, *g;
15461     int nItem;
15462     static int secondTime = FALSE;
15463
15464     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15465         DisplayError(_("Game list not loaded or empty"), 0);
15466         return;
15467     }
15468
15469     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15470         fclose(g);
15471         secondTime++;
15472         DisplayNote(_("Book file exists! Try again for overwrite."));
15473         return;
15474     }
15475
15476     creatingBook = TRUE;
15477     secondTime = FALSE;
15478
15479     /* Get list size */
15480     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15481         LoadGame(f, nItem, "", TRUE);
15482         AddGameToBook(TRUE);
15483         lg = (ListGame *) lg->node.succ;
15484     }
15485
15486     creatingBook = FALSE;
15487     FlushBook();
15488 }
15489
15490 void
15491 BookEvent ()
15492 {
15493     if (appData.noChessProgram) return;
15494     switch (gameMode) {
15495       case MachinePlaysWhite:
15496         if (WhiteOnMove(forwardMostMove)) {
15497             DisplayError(_("Wait until your turn."), 0);
15498             return;
15499         }
15500         break;
15501       case BeginningOfGame:
15502       case MachinePlaysBlack:
15503         if (!WhiteOnMove(forwardMostMove)) {
15504             DisplayError(_("Wait until your turn."), 0);
15505             return;
15506         }
15507         break;
15508       case EditPosition:
15509         EditPositionDone(TRUE);
15510         break;
15511       case TwoMachinesPlay:
15512         return;
15513       default:
15514         break;
15515     }
15516     SendToProgram("bk\n", &first);
15517     bookOutput[0] = NULLCHAR;
15518     bookRequested = TRUE;
15519 }
15520
15521 void
15522 AboutGameEvent ()
15523 {
15524     char *tags = PGNTags(&gameInfo);
15525     TagsPopUp(tags, CmailMsg());
15526     free(tags);
15527 }
15528
15529 /* end button procedures */
15530
15531 void
15532 PrintPosition (FILE *fp, int move)
15533 {
15534     int i, j;
15535
15536     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15537         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15538             char c = PieceToChar(boards[move][i][j]);
15539             fputc(c == 'x' ? '.' : c, fp);
15540             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15541         }
15542     }
15543     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15544       fprintf(fp, "white to play\n");
15545     else
15546       fprintf(fp, "black to play\n");
15547 }
15548
15549 void
15550 PrintOpponents (FILE *fp)
15551 {
15552     if (gameInfo.white != NULL) {
15553         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15554     } else {
15555         fprintf(fp, "\n");
15556     }
15557 }
15558
15559 /* Find last component of program's own name, using some heuristics */
15560 void
15561 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15562 {
15563     char *p, *q, c;
15564     int local = (strcmp(host, "localhost") == 0);
15565     while (!local && (p = strchr(prog, ';')) != NULL) {
15566         p++;
15567         while (*p == ' ') p++;
15568         prog = p;
15569     }
15570     if (*prog == '"' || *prog == '\'') {
15571         q = strchr(prog + 1, *prog);
15572     } else {
15573         q = strchr(prog, ' ');
15574     }
15575     if (q == NULL) q = prog + strlen(prog);
15576     p = q;
15577     while (p >= prog && *p != '/' && *p != '\\') p--;
15578     p++;
15579     if(p == prog && *p == '"') p++;
15580     c = *q; *q = 0;
15581     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15582     memcpy(buf, p, q - p);
15583     buf[q - p] = NULLCHAR;
15584     if (!local) {
15585         strcat(buf, "@");
15586         strcat(buf, host);
15587     }
15588 }
15589
15590 char *
15591 TimeControlTagValue ()
15592 {
15593     char buf[MSG_SIZ];
15594     if (!appData.clockMode) {
15595       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15596     } else if (movesPerSession > 0) {
15597       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15598     } else if (timeIncrement == 0) {
15599       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15600     } else {
15601       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15602     }
15603     return StrSave(buf);
15604 }
15605
15606 void
15607 SetGameInfo ()
15608 {
15609     /* This routine is used only for certain modes */
15610     VariantClass v = gameInfo.variant;
15611     ChessMove r = GameUnfinished;
15612     char *p = NULL;
15613
15614     if(keepInfo) return;
15615
15616     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15617         r = gameInfo.result;
15618         p = gameInfo.resultDetails;
15619         gameInfo.resultDetails = NULL;
15620     }
15621     ClearGameInfo(&gameInfo);
15622     gameInfo.variant = v;
15623
15624     switch (gameMode) {
15625       case MachinePlaysWhite:
15626         gameInfo.event = StrSave( appData.pgnEventHeader );
15627         gameInfo.site = StrSave(HostName());
15628         gameInfo.date = PGNDate();
15629         gameInfo.round = StrSave("-");
15630         gameInfo.white = StrSave(first.tidy);
15631         gameInfo.black = StrSave(UserName());
15632         gameInfo.timeControl = TimeControlTagValue();
15633         break;
15634
15635       case MachinePlaysBlack:
15636         gameInfo.event = StrSave( appData.pgnEventHeader );
15637         gameInfo.site = StrSave(HostName());
15638         gameInfo.date = PGNDate();
15639         gameInfo.round = StrSave("-");
15640         gameInfo.white = StrSave(UserName());
15641         gameInfo.black = StrSave(first.tidy);
15642         gameInfo.timeControl = TimeControlTagValue();
15643         break;
15644
15645       case TwoMachinesPlay:
15646         gameInfo.event = StrSave( appData.pgnEventHeader );
15647         gameInfo.site = StrSave(HostName());
15648         gameInfo.date = PGNDate();
15649         if (roundNr > 0) {
15650             char buf[MSG_SIZ];
15651             snprintf(buf, MSG_SIZ, "%d", roundNr);
15652             gameInfo.round = StrSave(buf);
15653         } else {
15654             gameInfo.round = StrSave("-");
15655         }
15656         if (first.twoMachinesColor[0] == 'w') {
15657             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15658             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15659         } else {
15660             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15661             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15662         }
15663         gameInfo.timeControl = TimeControlTagValue();
15664         break;
15665
15666       case EditGame:
15667         gameInfo.event = StrSave("Edited game");
15668         gameInfo.site = StrSave(HostName());
15669         gameInfo.date = PGNDate();
15670         gameInfo.round = StrSave("-");
15671         gameInfo.white = StrSave("-");
15672         gameInfo.black = StrSave("-");
15673         gameInfo.result = r;
15674         gameInfo.resultDetails = p;
15675         break;
15676
15677       case EditPosition:
15678         gameInfo.event = StrSave("Edited position");
15679         gameInfo.site = StrSave(HostName());
15680         gameInfo.date = PGNDate();
15681         gameInfo.round = StrSave("-");
15682         gameInfo.white = StrSave("-");
15683         gameInfo.black = StrSave("-");
15684         break;
15685
15686       case IcsPlayingWhite:
15687       case IcsPlayingBlack:
15688       case IcsObserving:
15689       case IcsExamining:
15690         break;
15691
15692       case PlayFromGameFile:
15693         gameInfo.event = StrSave("Game from non-PGN file");
15694         gameInfo.site = StrSave(HostName());
15695         gameInfo.date = PGNDate();
15696         gameInfo.round = StrSave("-");
15697         gameInfo.white = StrSave("?");
15698         gameInfo.black = StrSave("?");
15699         break;
15700
15701       default:
15702         break;
15703     }
15704 }
15705
15706 void
15707 ReplaceComment (int index, char *text)
15708 {
15709     int len;
15710     char *p;
15711     float score;
15712
15713     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15714        pvInfoList[index-1].depth == len &&
15715        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15716        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15717     while (*text == '\n') text++;
15718     len = strlen(text);
15719     while (len > 0 && text[len - 1] == '\n') len--;
15720
15721     if (commentList[index] != NULL)
15722       free(commentList[index]);
15723
15724     if (len == 0) {
15725         commentList[index] = NULL;
15726         return;
15727     }
15728   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15729       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15730       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15731     commentList[index] = (char *) malloc(len + 2);
15732     strncpy(commentList[index], text, len);
15733     commentList[index][len] = '\n';
15734     commentList[index][len + 1] = NULLCHAR;
15735   } else {
15736     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15737     char *p;
15738     commentList[index] = (char *) malloc(len + 7);
15739     safeStrCpy(commentList[index], "{\n", 3);
15740     safeStrCpy(commentList[index]+2, text, len+1);
15741     commentList[index][len+2] = NULLCHAR;
15742     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15743     strcat(commentList[index], "\n}\n");
15744   }
15745 }
15746
15747 void
15748 CrushCRs (char *text)
15749 {
15750   char *p = text;
15751   char *q = text;
15752   char ch;
15753
15754   do {
15755     ch = *p++;
15756     if (ch == '\r') continue;
15757     *q++ = ch;
15758   } while (ch != '\0');
15759 }
15760
15761 void
15762 AppendComment (int index, char *text, Boolean addBraces)
15763 /* addBraces  tells if we should add {} */
15764 {
15765     int oldlen, len;
15766     char *old;
15767
15768 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15769     if(addBraces == 3) addBraces = 0; else // force appending literally
15770     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15771
15772     CrushCRs(text);
15773     while (*text == '\n') text++;
15774     len = strlen(text);
15775     while (len > 0 && text[len - 1] == '\n') len--;
15776     text[len] = NULLCHAR;
15777
15778     if (len == 0) return;
15779
15780     if (commentList[index] != NULL) {
15781       Boolean addClosingBrace = addBraces;
15782         old = commentList[index];
15783         oldlen = strlen(old);
15784         while(commentList[index][oldlen-1] ==  '\n')
15785           commentList[index][--oldlen] = NULLCHAR;
15786         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15787         safeStrCpy(commentList[index], old, oldlen + len + 6);
15788         free(old);
15789         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15790         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15791           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15792           while (*text == '\n') { text++; len--; }
15793           commentList[index][--oldlen] = NULLCHAR;
15794       }
15795         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15796         else          strcat(commentList[index], "\n");
15797         strcat(commentList[index], text);
15798         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15799         else          strcat(commentList[index], "\n");
15800     } else {
15801         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15802         if(addBraces)
15803           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15804         else commentList[index][0] = NULLCHAR;
15805         strcat(commentList[index], text);
15806         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15807         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15808     }
15809 }
15810
15811 static char *
15812 FindStr (char * text, char * sub_text)
15813 {
15814     char * result = strstr( text, sub_text );
15815
15816     if( result != NULL ) {
15817         result += strlen( sub_text );
15818     }
15819
15820     return result;
15821 }
15822
15823 /* [AS] Try to extract PV info from PGN comment */
15824 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15825 char *
15826 GetInfoFromComment (int index, char * text)
15827 {
15828     char * sep = text, *p;
15829
15830     if( text != NULL && index > 0 ) {
15831         int score = 0;
15832         int depth = 0;
15833         int time = -1, sec = 0, deci;
15834         char * s_eval = FindStr( text, "[%eval " );
15835         char * s_emt = FindStr( text, "[%emt " );
15836 #if 0
15837         if( s_eval != NULL || s_emt != NULL ) {
15838 #else
15839         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15840 #endif
15841             /* New style */
15842             char delim;
15843
15844             if( s_eval != NULL ) {
15845                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15846                     return text;
15847                 }
15848
15849                 if( delim != ']' ) {
15850                     return text;
15851                 }
15852             }
15853
15854             if( s_emt != NULL ) {
15855             }
15856                 return text;
15857         }
15858         else {
15859             /* We expect something like: [+|-]nnn.nn/dd */
15860             int score_lo = 0;
15861
15862             if(*text != '{') return text; // [HGM] braces: must be normal comment
15863
15864             sep = strchr( text, '/' );
15865             if( sep == NULL || sep < (text+4) ) {
15866                 return text;
15867             }
15868
15869             p = text;
15870             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15871             if(p[1] == '(') { // comment starts with PV
15872                p = strchr(p, ')'); // locate end of PV
15873                if(p == NULL || sep < p+5) return text;
15874                // at this point we have something like "{(.*) +0.23/6 ..."
15875                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15876                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15877                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15878             }
15879             time = -1; sec = -1; deci = -1;
15880             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15881                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15882                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15883                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15884                 return text;
15885             }
15886
15887             if( score_lo < 0 || score_lo >= 100 ) {
15888                 return text;
15889             }
15890
15891             if(sec >= 0) time = 600*time + 10*sec; else
15892             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15893
15894             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15895
15896             /* [HGM] PV time: now locate end of PV info */
15897             while( *++sep >= '0' && *sep <= '9'); // strip depth
15898             if(time >= 0)
15899             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15900             if(sec >= 0)
15901             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15902             if(deci >= 0)
15903             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15904             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15905         }
15906
15907         if( depth <= 0 ) {
15908             return text;
15909         }
15910
15911         if( time < 0 ) {
15912             time = -1;
15913         }
15914
15915         pvInfoList[index-1].depth = depth;
15916         pvInfoList[index-1].score = score;
15917         pvInfoList[index-1].time  = 10*time; // centi-sec
15918         if(*sep == '}') *sep = 0; else *--sep = '{';
15919         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15920     }
15921     return sep;
15922 }
15923
15924 void
15925 SendToProgram (char *message, ChessProgramState *cps)
15926 {
15927     int count, outCount, error;
15928     char buf[MSG_SIZ];
15929
15930     if (cps->pr == NoProc) return;
15931     Attention(cps);
15932
15933     if (appData.debugMode) {
15934         TimeMark now;
15935         GetTimeMark(&now);
15936         fprintf(debugFP, "%ld >%-6s: %s",
15937                 SubtractTimeMarks(&now, &programStartTime),
15938                 cps->which, message);
15939         if(serverFP)
15940             fprintf(serverFP, "%ld >%-6s: %s",
15941                 SubtractTimeMarks(&now, &programStartTime),
15942                 cps->which, message), fflush(serverFP);
15943     }
15944
15945     count = strlen(message);
15946     outCount = OutputToProcess(cps->pr, message, count, &error);
15947     if (outCount < count && !exiting
15948                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15949       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15950       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15951         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15952             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15953                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15954                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15955                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15956             } else {
15957                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15958                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15959                 gameInfo.result = res;
15960             }
15961             gameInfo.resultDetails = StrSave(buf);
15962         }
15963         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15964         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15965     }
15966 }
15967
15968 void
15969 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15970 {
15971     char *end_str;
15972     char buf[MSG_SIZ];
15973     ChessProgramState *cps = (ChessProgramState *)closure;
15974
15975     if (isr != cps->isr) return; /* Killed intentionally */
15976     if (count <= 0) {
15977         if (count == 0) {
15978             RemoveInputSource(cps->isr);
15979             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15980                     _(cps->which), cps->program);
15981             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15982             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15983                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15984                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15985                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15986                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15987                 } else {
15988                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15989                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15990                     gameInfo.result = res;
15991                 }
15992                 gameInfo.resultDetails = StrSave(buf);
15993             }
15994             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15995             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15996         } else {
15997             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15998                     _(cps->which), cps->program);
15999             RemoveInputSource(cps->isr);
16000
16001             /* [AS] Program is misbehaving badly... kill it */
16002             if( count == -2 ) {
16003                 DestroyChildProcess( cps->pr, 9 );
16004                 cps->pr = NoProc;
16005             }
16006
16007             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16008         }
16009         return;
16010     }
16011
16012     if ((end_str = strchr(message, '\r')) != NULL)
16013       *end_str = NULLCHAR;
16014     if ((end_str = strchr(message, '\n')) != NULL)
16015       *end_str = NULLCHAR;
16016
16017     if (appData.debugMode) {
16018         TimeMark now; int print = 1;
16019         char *quote = ""; char c; int i;
16020
16021         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16022                 char start = message[0];
16023                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16024                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16025                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16026                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16027                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16028                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16029                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16030                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16031                    sscanf(message, "hint: %c", &c)!=1 &&
16032                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16033                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16034                     print = (appData.engineComments >= 2);
16035                 }
16036                 message[0] = start; // restore original message
16037         }
16038         if(print) {
16039                 GetTimeMark(&now);
16040                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16041                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16042                         quote,
16043                         message);
16044                 if(serverFP)
16045                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16046                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16047                         quote,
16048                         message), fflush(serverFP);
16049         }
16050     }
16051
16052     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16053     if (appData.icsEngineAnalyze) {
16054         if (strstr(message, "whisper") != NULL ||
16055              strstr(message, "kibitz") != NULL ||
16056             strstr(message, "tellics") != NULL) return;
16057     }
16058
16059     HandleMachineMove(message, cps);
16060 }
16061
16062
16063 void
16064 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16065 {
16066     char buf[MSG_SIZ];
16067     int seconds;
16068
16069     if( timeControl_2 > 0 ) {
16070         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16071             tc = timeControl_2;
16072         }
16073     }
16074     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16075     inc /= cps->timeOdds;
16076     st  /= cps->timeOdds;
16077
16078     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16079
16080     if (st > 0) {
16081       /* Set exact time per move, normally using st command */
16082       if (cps->stKludge) {
16083         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16084         seconds = st % 60;
16085         if (seconds == 0) {
16086           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16087         } else {
16088           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16089         }
16090       } else {
16091         snprintf(buf, MSG_SIZ, "st %d\n", st);
16092       }
16093     } else {
16094       /* Set conventional or incremental time control, using level command */
16095       if (seconds == 0) {
16096         /* Note old gnuchess bug -- minutes:seconds used to not work.
16097            Fixed in later versions, but still avoid :seconds
16098            when seconds is 0. */
16099         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16100       } else {
16101         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16102                  seconds, inc/1000.);
16103       }
16104     }
16105     SendToProgram(buf, cps);
16106
16107     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16108     /* Orthogonally, limit search to given depth */
16109     if (sd > 0) {
16110       if (cps->sdKludge) {
16111         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16112       } else {
16113         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16114       }
16115       SendToProgram(buf, cps);
16116     }
16117
16118     if(cps->nps >= 0) { /* [HGM] nps */
16119         if(cps->supportsNPS == FALSE)
16120           cps->nps = -1; // don't use if engine explicitly says not supported!
16121         else {
16122           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16123           SendToProgram(buf, cps);
16124         }
16125     }
16126 }
16127
16128 ChessProgramState *
16129 WhitePlayer ()
16130 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16131 {
16132     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16133        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16134         return &second;
16135     return &first;
16136 }
16137
16138 void
16139 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16140 {
16141     char message[MSG_SIZ];
16142     long time, otime;
16143
16144     /* Note: this routine must be called when the clocks are stopped
16145        or when they have *just* been set or switched; otherwise
16146        it will be off by the time since the current tick started.
16147     */
16148     if (machineWhite) {
16149         time = whiteTimeRemaining / 10;
16150         otime = blackTimeRemaining / 10;
16151     } else {
16152         time = blackTimeRemaining / 10;
16153         otime = whiteTimeRemaining / 10;
16154     }
16155     /* [HGM] translate opponent's time by time-odds factor */
16156     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16157
16158     if (time <= 0) time = 1;
16159     if (otime <= 0) otime = 1;
16160
16161     snprintf(message, MSG_SIZ, "time %ld\n", time);
16162     SendToProgram(message, cps);
16163
16164     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16165     SendToProgram(message, cps);
16166 }
16167
16168 char *
16169 EngineDefinedVariant (ChessProgramState *cps, int n)
16170 {   // return name of n-th unknown variant that engine supports
16171     static char buf[MSG_SIZ];
16172     char *p, *s = cps->variants;
16173     if(!s) return NULL;
16174     do { // parse string from variants feature
16175       VariantClass v;
16176         p = strchr(s, ',');
16177         if(p) *p = NULLCHAR;
16178       v = StringToVariant(s);
16179       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16180         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16181             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16182         }
16183         if(p) *p++ = ',';
16184         if(n < 0) return buf;
16185     } while(s = p);
16186     return NULL;
16187 }
16188
16189 int
16190 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16191 {
16192   char buf[MSG_SIZ];
16193   int len = strlen(name);
16194   int val;
16195
16196   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16197     (*p) += len + 1;
16198     sscanf(*p, "%d", &val);
16199     *loc = (val != 0);
16200     while (**p && **p != ' ')
16201       (*p)++;
16202     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16203     SendToProgram(buf, cps);
16204     return TRUE;
16205   }
16206   return FALSE;
16207 }
16208
16209 int
16210 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16211 {
16212   char buf[MSG_SIZ];
16213   int len = strlen(name);
16214   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16215     (*p) += len + 1;
16216     sscanf(*p, "%d", loc);
16217     while (**p && **p != ' ') (*p)++;
16218     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16219     SendToProgram(buf, cps);
16220     return TRUE;
16221   }
16222   return FALSE;
16223 }
16224
16225 int
16226 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16227 {
16228   char buf[MSG_SIZ];
16229   int len = strlen(name);
16230   if (strncmp((*p), name, len) == 0
16231       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16232     (*p) += len + 2;
16233     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16234     sscanf(*p, "%[^\"]", *loc);
16235     while (**p && **p != '\"') (*p)++;
16236     if (**p == '\"') (*p)++;
16237     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16238     SendToProgram(buf, cps);
16239     return TRUE;
16240   }
16241   return FALSE;
16242 }
16243
16244 int
16245 ParseOption (Option *opt, ChessProgramState *cps)
16246 // [HGM] options: process the string that defines an engine option, and determine
16247 // name, type, default value, and allowed value range
16248 {
16249         char *p, *q, buf[MSG_SIZ];
16250         int n, min = (-1)<<31, max = 1<<31, def;
16251
16252         if(p = strstr(opt->name, " -spin ")) {
16253             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16254             if(max < min) max = min; // enforce consistency
16255             if(def < min) def = min;
16256             if(def > max) def = max;
16257             opt->value = def;
16258             opt->min = min;
16259             opt->max = max;
16260             opt->type = Spin;
16261         } else if((p = strstr(opt->name, " -slider "))) {
16262             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16263             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16264             if(max < min) max = min; // enforce consistency
16265             if(def < min) def = min;
16266             if(def > max) def = max;
16267             opt->value = def;
16268             opt->min = min;
16269             opt->max = max;
16270             opt->type = Spin; // Slider;
16271         } else if((p = strstr(opt->name, " -string "))) {
16272             opt->textValue = p+9;
16273             opt->type = TextBox;
16274         } else if((p = strstr(opt->name, " -file "))) {
16275             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16276             opt->textValue = p+7;
16277             opt->type = FileName; // FileName;
16278         } else if((p = strstr(opt->name, " -path "))) {
16279             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16280             opt->textValue = p+7;
16281             opt->type = PathName; // PathName;
16282         } else if(p = strstr(opt->name, " -check ")) {
16283             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16284             opt->value = (def != 0);
16285             opt->type = CheckBox;
16286         } else if(p = strstr(opt->name, " -combo ")) {
16287             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16288             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16289             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16290             opt->value = n = 0;
16291             while(q = StrStr(q, " /// ")) {
16292                 n++; *q = 0;    // count choices, and null-terminate each of them
16293                 q += 5;
16294                 if(*q == '*') { // remember default, which is marked with * prefix
16295                     q++;
16296                     opt->value = n;
16297                 }
16298                 cps->comboList[cps->comboCnt++] = q;
16299             }
16300             cps->comboList[cps->comboCnt++] = NULL;
16301             opt->max = n + 1;
16302             opt->type = ComboBox;
16303         } else if(p = strstr(opt->name, " -button")) {
16304             opt->type = Button;
16305         } else if(p = strstr(opt->name, " -save")) {
16306             opt->type = SaveButton;
16307         } else return FALSE;
16308         *p = 0; // terminate option name
16309         // now look if the command-line options define a setting for this engine option.
16310         if(cps->optionSettings && cps->optionSettings[0])
16311             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16312         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16313           snprintf(buf, MSG_SIZ, "option %s", p);
16314                 if(p = strstr(buf, ",")) *p = 0;
16315                 if(q = strchr(buf, '=')) switch(opt->type) {
16316                     case ComboBox:
16317                         for(n=0; n<opt->max; n++)
16318                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16319                         break;
16320                     case TextBox:
16321                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16322                         break;
16323                     case Spin:
16324                     case CheckBox:
16325                         opt->value = atoi(q+1);
16326                     default:
16327                         break;
16328                 }
16329                 strcat(buf, "\n");
16330                 SendToProgram(buf, cps);
16331         }
16332         return TRUE;
16333 }
16334
16335 void
16336 FeatureDone (ChessProgramState *cps, int val)
16337 {
16338   DelayedEventCallback cb = GetDelayedEvent();
16339   if ((cb == InitBackEnd3 && cps == &first) ||
16340       (cb == SettingsMenuIfReady && cps == &second) ||
16341       (cb == LoadEngine) ||
16342       (cb == TwoMachinesEventIfReady)) {
16343     CancelDelayedEvent();
16344     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16345   }
16346   cps->initDone = val;
16347   if(val) cps->reload = FALSE;
16348 }
16349
16350 /* Parse feature command from engine */
16351 void
16352 ParseFeatures (char *args, ChessProgramState *cps)
16353 {
16354   char *p = args;
16355   char *q = NULL;
16356   int val;
16357   char buf[MSG_SIZ];
16358
16359   for (;;) {
16360     while (*p == ' ') p++;
16361     if (*p == NULLCHAR) return;
16362
16363     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16364     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16365     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16366     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16367     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16368     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16369     if (BoolFeature(&p, "reuse", &val, cps)) {
16370       /* Engine can disable reuse, but can't enable it if user said no */
16371       if (!val) cps->reuse = FALSE;
16372       continue;
16373     }
16374     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16375     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16376       if (gameMode == TwoMachinesPlay) {
16377         DisplayTwoMachinesTitle();
16378       } else {
16379         DisplayTitle("");
16380       }
16381       continue;
16382     }
16383     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16384     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16385     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16386     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16387     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16388     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16389     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16390     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16391     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16392     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16393     if (IntFeature(&p, "done", &val, cps)) {
16394       FeatureDone(cps, val);
16395       continue;
16396     }
16397     /* Added by Tord: */
16398     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16399     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16400     /* End of additions by Tord */
16401
16402     /* [HGM] added features: */
16403     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16404     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16405     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16406     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16407     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16408     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16409     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16410     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16411         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16412         FREE(cps->option[cps->nrOptions].name);
16413         cps->option[cps->nrOptions].name = q; q = NULL;
16414         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16415           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16416             SendToProgram(buf, cps);
16417             continue;
16418         }
16419         if(cps->nrOptions >= MAX_OPTIONS) {
16420             cps->nrOptions--;
16421             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16422             DisplayError(buf, 0);
16423         }
16424         continue;
16425     }
16426     /* End of additions by HGM */
16427
16428     /* unknown feature: complain and skip */
16429     q = p;
16430     while (*q && *q != '=') q++;
16431     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16432     SendToProgram(buf, cps);
16433     p = q;
16434     if (*p == '=') {
16435       p++;
16436       if (*p == '\"') {
16437         p++;
16438         while (*p && *p != '\"') p++;
16439         if (*p == '\"') p++;
16440       } else {
16441         while (*p && *p != ' ') p++;
16442       }
16443     }
16444   }
16445
16446 }
16447
16448 void
16449 PeriodicUpdatesEvent (int newState)
16450 {
16451     if (newState == appData.periodicUpdates)
16452       return;
16453
16454     appData.periodicUpdates=newState;
16455
16456     /* Display type changes, so update it now */
16457 //    DisplayAnalysis();
16458
16459     /* Get the ball rolling again... */
16460     if (newState) {
16461         AnalysisPeriodicEvent(1);
16462         StartAnalysisClock();
16463     }
16464 }
16465
16466 void
16467 PonderNextMoveEvent (int newState)
16468 {
16469     if (newState == appData.ponderNextMove) return;
16470     if (gameMode == EditPosition) EditPositionDone(TRUE);
16471     if (newState) {
16472         SendToProgram("hard\n", &first);
16473         if (gameMode == TwoMachinesPlay) {
16474             SendToProgram("hard\n", &second);
16475         }
16476     } else {
16477         SendToProgram("easy\n", &first);
16478         thinkOutput[0] = NULLCHAR;
16479         if (gameMode == TwoMachinesPlay) {
16480             SendToProgram("easy\n", &second);
16481         }
16482     }
16483     appData.ponderNextMove = newState;
16484 }
16485
16486 void
16487 NewSettingEvent (int option, int *feature, char *command, int value)
16488 {
16489     char buf[MSG_SIZ];
16490
16491     if (gameMode == EditPosition) EditPositionDone(TRUE);
16492     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16493     if(feature == NULL || *feature) SendToProgram(buf, &first);
16494     if (gameMode == TwoMachinesPlay) {
16495         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16496     }
16497 }
16498
16499 void
16500 ShowThinkingEvent ()
16501 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16502 {
16503     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16504     int newState = appData.showThinking
16505         // [HGM] thinking: other features now need thinking output as well
16506         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16507
16508     if (oldState == newState) return;
16509     oldState = newState;
16510     if (gameMode == EditPosition) EditPositionDone(TRUE);
16511     if (oldState) {
16512         SendToProgram("post\n", &first);
16513         if (gameMode == TwoMachinesPlay) {
16514             SendToProgram("post\n", &second);
16515         }
16516     } else {
16517         SendToProgram("nopost\n", &first);
16518         thinkOutput[0] = NULLCHAR;
16519         if (gameMode == TwoMachinesPlay) {
16520             SendToProgram("nopost\n", &second);
16521         }
16522     }
16523 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16524 }
16525
16526 void
16527 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16528 {
16529   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16530   if (pr == NoProc) return;
16531   AskQuestion(title, question, replyPrefix, pr);
16532 }
16533
16534 void
16535 TypeInEvent (char firstChar)
16536 {
16537     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16538         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16539         gameMode == AnalyzeMode || gameMode == EditGame ||
16540         gameMode == EditPosition || gameMode == IcsExamining ||
16541         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16542         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16543                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16544                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16545         gameMode == Training) PopUpMoveDialog(firstChar);
16546 }
16547
16548 void
16549 TypeInDoneEvent (char *move)
16550 {
16551         Board board;
16552         int n, fromX, fromY, toX, toY;
16553         char promoChar;
16554         ChessMove moveType;
16555
16556         // [HGM] FENedit
16557         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16558                 EditPositionPasteFEN(move);
16559                 return;
16560         }
16561         // [HGM] movenum: allow move number to be typed in any mode
16562         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16563           ToNrEvent(2*n-1);
16564           return;
16565         }
16566         // undocumented kludge: allow command-line option to be typed in!
16567         // (potentially fatal, and does not implement the effect of the option.)
16568         // should only be used for options that are values on which future decisions will be made,
16569         // and definitely not on options that would be used during initialization.
16570         if(strstr(move, "!!! -") == move) {
16571             ParseArgsFromString(move+4);
16572             return;
16573         }
16574
16575       if (gameMode != EditGame && currentMove != forwardMostMove &&
16576         gameMode != Training) {
16577         DisplayMoveError(_("Displayed move is not current"));
16578       } else {
16579         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16580           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16581         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16582         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16583           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16584           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16585         } else {
16586           DisplayMoveError(_("Could not parse move"));
16587         }
16588       }
16589 }
16590
16591 void
16592 DisplayMove (int moveNumber)
16593 {
16594     char message[MSG_SIZ];
16595     char res[MSG_SIZ];
16596     char cpThinkOutput[MSG_SIZ];
16597
16598     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16599
16600     if (moveNumber == forwardMostMove - 1 ||
16601         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16602
16603         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16604
16605         if (strchr(cpThinkOutput, '\n')) {
16606             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16607         }
16608     } else {
16609         *cpThinkOutput = NULLCHAR;
16610     }
16611
16612     /* [AS] Hide thinking from human user */
16613     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16614         *cpThinkOutput = NULLCHAR;
16615         if( thinkOutput[0] != NULLCHAR ) {
16616             int i;
16617
16618             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16619                 cpThinkOutput[i] = '.';
16620             }
16621             cpThinkOutput[i] = NULLCHAR;
16622             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16623         }
16624     }
16625
16626     if (moveNumber == forwardMostMove - 1 &&
16627         gameInfo.resultDetails != NULL) {
16628         if (gameInfo.resultDetails[0] == NULLCHAR) {
16629           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16630         } else {
16631           snprintf(res, MSG_SIZ, " {%s} %s",
16632                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16633         }
16634     } else {
16635         res[0] = NULLCHAR;
16636     }
16637
16638     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16639         DisplayMessage(res, cpThinkOutput);
16640     } else {
16641       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16642                 WhiteOnMove(moveNumber) ? " " : ".. ",
16643                 parseList[moveNumber], res);
16644         DisplayMessage(message, cpThinkOutput);
16645     }
16646 }
16647
16648 void
16649 DisplayComment (int moveNumber, char *text)
16650 {
16651     char title[MSG_SIZ];
16652
16653     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16654       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16655     } else {
16656       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16657               WhiteOnMove(moveNumber) ? " " : ".. ",
16658               parseList[moveNumber]);
16659     }
16660     if (text != NULL && (appData.autoDisplayComment || commentUp))
16661         CommentPopUp(title, text);
16662 }
16663
16664 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16665  * might be busy thinking or pondering.  It can be omitted if your
16666  * gnuchess is configured to stop thinking immediately on any user
16667  * input.  However, that gnuchess feature depends on the FIONREAD
16668  * ioctl, which does not work properly on some flavors of Unix.
16669  */
16670 void
16671 Attention (ChessProgramState *cps)
16672 {
16673 #if ATTENTION
16674     if (!cps->useSigint) return;
16675     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16676     switch (gameMode) {
16677       case MachinePlaysWhite:
16678       case MachinePlaysBlack:
16679       case TwoMachinesPlay:
16680       case IcsPlayingWhite:
16681       case IcsPlayingBlack:
16682       case AnalyzeMode:
16683       case AnalyzeFile:
16684         /* Skip if we know it isn't thinking */
16685         if (!cps->maybeThinking) return;
16686         if (appData.debugMode)
16687           fprintf(debugFP, "Interrupting %s\n", cps->which);
16688         InterruptChildProcess(cps->pr);
16689         cps->maybeThinking = FALSE;
16690         break;
16691       default:
16692         break;
16693     }
16694 #endif /*ATTENTION*/
16695 }
16696
16697 int
16698 CheckFlags ()
16699 {
16700     if (whiteTimeRemaining <= 0) {
16701         if (!whiteFlag) {
16702             whiteFlag = TRUE;
16703             if (appData.icsActive) {
16704                 if (appData.autoCallFlag &&
16705                     gameMode == IcsPlayingBlack && !blackFlag) {
16706                   SendToICS(ics_prefix);
16707                   SendToICS("flag\n");
16708                 }
16709             } else {
16710                 if (blackFlag) {
16711                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16712                 } else {
16713                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16714                     if (appData.autoCallFlag) {
16715                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16716                         return TRUE;
16717                     }
16718                 }
16719             }
16720         }
16721     }
16722     if (blackTimeRemaining <= 0) {
16723         if (!blackFlag) {
16724             blackFlag = TRUE;
16725             if (appData.icsActive) {
16726                 if (appData.autoCallFlag &&
16727                     gameMode == IcsPlayingWhite && !whiteFlag) {
16728                   SendToICS(ics_prefix);
16729                   SendToICS("flag\n");
16730                 }
16731             } else {
16732                 if (whiteFlag) {
16733                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16734                 } else {
16735                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16736                     if (appData.autoCallFlag) {
16737                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16738                         return TRUE;
16739                     }
16740                 }
16741             }
16742         }
16743     }
16744     return FALSE;
16745 }
16746
16747 void
16748 CheckTimeControl ()
16749 {
16750     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16751         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16752
16753     /*
16754      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16755      */
16756     if ( !WhiteOnMove(forwardMostMove) ) {
16757         /* White made time control */
16758         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16759         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16760         /* [HGM] time odds: correct new time quota for time odds! */
16761                                             / WhitePlayer()->timeOdds;
16762         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16763     } else {
16764         lastBlack -= blackTimeRemaining;
16765         /* Black made time control */
16766         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16767                                             / WhitePlayer()->other->timeOdds;
16768         lastWhite = whiteTimeRemaining;
16769     }
16770 }
16771
16772 void
16773 DisplayBothClocks ()
16774 {
16775     int wom = gameMode == EditPosition ?
16776       !blackPlaysFirst : WhiteOnMove(currentMove);
16777     DisplayWhiteClock(whiteTimeRemaining, wom);
16778     DisplayBlackClock(blackTimeRemaining, !wom);
16779 }
16780
16781
16782 /* Timekeeping seems to be a portability nightmare.  I think everyone
16783    has ftime(), but I'm really not sure, so I'm including some ifdefs
16784    to use other calls if you don't.  Clocks will be less accurate if
16785    you have neither ftime nor gettimeofday.
16786 */
16787
16788 /* VS 2008 requires the #include outside of the function */
16789 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16790 #include <sys/timeb.h>
16791 #endif
16792
16793 /* Get the current time as a TimeMark */
16794 void
16795 GetTimeMark (TimeMark *tm)
16796 {
16797 #if HAVE_GETTIMEOFDAY
16798
16799     struct timeval timeVal;
16800     struct timezone timeZone;
16801
16802     gettimeofday(&timeVal, &timeZone);
16803     tm->sec = (long) timeVal.tv_sec;
16804     tm->ms = (int) (timeVal.tv_usec / 1000L);
16805
16806 #else /*!HAVE_GETTIMEOFDAY*/
16807 #if HAVE_FTIME
16808
16809 // include <sys/timeb.h> / moved to just above start of function
16810     struct timeb timeB;
16811
16812     ftime(&timeB);
16813     tm->sec = (long) timeB.time;
16814     tm->ms = (int) timeB.millitm;
16815
16816 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16817     tm->sec = (long) time(NULL);
16818     tm->ms = 0;
16819 #endif
16820 #endif
16821 }
16822
16823 /* Return the difference in milliseconds between two
16824    time marks.  We assume the difference will fit in a long!
16825 */
16826 long
16827 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16828 {
16829     return 1000L*(tm2->sec - tm1->sec) +
16830            (long) (tm2->ms - tm1->ms);
16831 }
16832
16833
16834 /*
16835  * Code to manage the game clocks.
16836  *
16837  * In tournament play, black starts the clock and then white makes a move.
16838  * We give the human user a slight advantage if he is playing white---the
16839  * clocks don't run until he makes his first move, so it takes zero time.
16840  * Also, we don't account for network lag, so we could get out of sync
16841  * with GNU Chess's clock -- but then, referees are always right.
16842  */
16843
16844 static TimeMark tickStartTM;
16845 static long intendedTickLength;
16846
16847 long
16848 NextTickLength (long timeRemaining)
16849 {
16850     long nominalTickLength, nextTickLength;
16851
16852     if (timeRemaining > 0L && timeRemaining <= 10000L)
16853       nominalTickLength = 100L;
16854     else
16855       nominalTickLength = 1000L;
16856     nextTickLength = timeRemaining % nominalTickLength;
16857     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16858
16859     return nextTickLength;
16860 }
16861
16862 /* Adjust clock one minute up or down */
16863 void
16864 AdjustClock (Boolean which, int dir)
16865 {
16866     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16867     if(which) blackTimeRemaining += 60000*dir;
16868     else      whiteTimeRemaining += 60000*dir;
16869     DisplayBothClocks();
16870     adjustedClock = TRUE;
16871 }
16872
16873 /* Stop clocks and reset to a fresh time control */
16874 void
16875 ResetClocks ()
16876 {
16877     (void) StopClockTimer();
16878     if (appData.icsActive) {
16879         whiteTimeRemaining = blackTimeRemaining = 0;
16880     } else if (searchTime) {
16881         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16882         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16883     } else { /* [HGM] correct new time quote for time odds */
16884         whiteTC = blackTC = fullTimeControlString;
16885         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16886         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16887     }
16888     if (whiteFlag || blackFlag) {
16889         DisplayTitle("");
16890         whiteFlag = blackFlag = FALSE;
16891     }
16892     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16893     DisplayBothClocks();
16894     adjustedClock = FALSE;
16895 }
16896
16897 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16898
16899 /* Decrement running clock by amount of time that has passed */
16900 void
16901 DecrementClocks ()
16902 {
16903     long timeRemaining;
16904     long lastTickLength, fudge;
16905     TimeMark now;
16906
16907     if (!appData.clockMode) return;
16908     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16909
16910     GetTimeMark(&now);
16911
16912     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16913
16914     /* Fudge if we woke up a little too soon */
16915     fudge = intendedTickLength - lastTickLength;
16916     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16917
16918     if (WhiteOnMove(forwardMostMove)) {
16919         if(whiteNPS >= 0) lastTickLength = 0;
16920         timeRemaining = whiteTimeRemaining -= lastTickLength;
16921         if(timeRemaining < 0 && !appData.icsActive) {
16922             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16923             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16924                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16925                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16926             }
16927         }
16928         DisplayWhiteClock(whiteTimeRemaining - fudge,
16929                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16930     } else {
16931         if(blackNPS >= 0) lastTickLength = 0;
16932         timeRemaining = blackTimeRemaining -= lastTickLength;
16933         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16934             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16935             if(suddenDeath) {
16936                 blackStartMove = forwardMostMove;
16937                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16938             }
16939         }
16940         DisplayBlackClock(blackTimeRemaining - fudge,
16941                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16942     }
16943     if (CheckFlags()) return;
16944
16945     if(twoBoards) { // count down secondary board's clocks as well
16946         activePartnerTime -= lastTickLength;
16947         partnerUp = 1;
16948         if(activePartner == 'W')
16949             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16950         else
16951             DisplayBlackClock(activePartnerTime, TRUE);
16952         partnerUp = 0;
16953     }
16954
16955     tickStartTM = now;
16956     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16957     StartClockTimer(intendedTickLength);
16958
16959     /* if the time remaining has fallen below the alarm threshold, sound the
16960      * alarm. if the alarm has sounded and (due to a takeback or time control
16961      * with increment) the time remaining has increased to a level above the
16962      * threshold, reset the alarm so it can sound again.
16963      */
16964
16965     if (appData.icsActive && appData.icsAlarm) {
16966
16967         /* make sure we are dealing with the user's clock */
16968         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16969                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16970            )) return;
16971
16972         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16973             alarmSounded = FALSE;
16974         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16975             PlayAlarmSound();
16976             alarmSounded = TRUE;
16977         }
16978     }
16979 }
16980
16981
16982 /* A player has just moved, so stop the previously running
16983    clock and (if in clock mode) start the other one.
16984    We redisplay both clocks in case we're in ICS mode, because
16985    ICS gives us an update to both clocks after every move.
16986    Note that this routine is called *after* forwardMostMove
16987    is updated, so the last fractional tick must be subtracted
16988    from the color that is *not* on move now.
16989 */
16990 void
16991 SwitchClocks (int newMoveNr)
16992 {
16993     long lastTickLength;
16994     TimeMark now;
16995     int flagged = FALSE;
16996
16997     GetTimeMark(&now);
16998
16999     if (StopClockTimer() && appData.clockMode) {
17000         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17001         if (!WhiteOnMove(forwardMostMove)) {
17002             if(blackNPS >= 0) lastTickLength = 0;
17003             blackTimeRemaining -= lastTickLength;
17004            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17005 //         if(pvInfoList[forwardMostMove].time == -1)
17006                  pvInfoList[forwardMostMove].time =               // use GUI time
17007                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17008         } else {
17009            if(whiteNPS >= 0) lastTickLength = 0;
17010            whiteTimeRemaining -= lastTickLength;
17011            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17012 //         if(pvInfoList[forwardMostMove].time == -1)
17013                  pvInfoList[forwardMostMove].time =
17014                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17015         }
17016         flagged = CheckFlags();
17017     }
17018     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17019     CheckTimeControl();
17020
17021     if (flagged || !appData.clockMode) return;
17022
17023     switch (gameMode) {
17024       case MachinePlaysBlack:
17025       case MachinePlaysWhite:
17026       case BeginningOfGame:
17027         if (pausing) return;
17028         break;
17029
17030       case EditGame:
17031       case PlayFromGameFile:
17032       case IcsExamining:
17033         return;
17034
17035       default:
17036         break;
17037     }
17038
17039     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17040         if(WhiteOnMove(forwardMostMove))
17041              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17042         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17043     }
17044
17045     tickStartTM = now;
17046     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17047       whiteTimeRemaining : blackTimeRemaining);
17048     StartClockTimer(intendedTickLength);
17049 }
17050
17051
17052 /* Stop both clocks */
17053 void
17054 StopClocks ()
17055 {
17056     long lastTickLength;
17057     TimeMark now;
17058
17059     if (!StopClockTimer()) return;
17060     if (!appData.clockMode) return;
17061
17062     GetTimeMark(&now);
17063
17064     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17065     if (WhiteOnMove(forwardMostMove)) {
17066         if(whiteNPS >= 0) lastTickLength = 0;
17067         whiteTimeRemaining -= lastTickLength;
17068         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17069     } else {
17070         if(blackNPS >= 0) lastTickLength = 0;
17071         blackTimeRemaining -= lastTickLength;
17072         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17073     }
17074     CheckFlags();
17075 }
17076
17077 /* Start clock of player on move.  Time may have been reset, so
17078    if clock is already running, stop and restart it. */
17079 void
17080 StartClocks ()
17081 {
17082     (void) StopClockTimer(); /* in case it was running already */
17083     DisplayBothClocks();
17084     if (CheckFlags()) return;
17085
17086     if (!appData.clockMode) return;
17087     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17088
17089     GetTimeMark(&tickStartTM);
17090     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17091       whiteTimeRemaining : blackTimeRemaining);
17092
17093    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17094     whiteNPS = blackNPS = -1;
17095     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17096        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17097         whiteNPS = first.nps;
17098     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17099        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17100         blackNPS = first.nps;
17101     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17102         whiteNPS = second.nps;
17103     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17104         blackNPS = second.nps;
17105     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17106
17107     StartClockTimer(intendedTickLength);
17108 }
17109
17110 char *
17111 TimeString (long ms)
17112 {
17113     long second, minute, hour, day;
17114     char *sign = "";
17115     static char buf[32];
17116
17117     if (ms > 0 && ms <= 9900) {
17118       /* convert milliseconds to tenths, rounding up */
17119       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17120
17121       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17122       return buf;
17123     }
17124
17125     /* convert milliseconds to seconds, rounding up */
17126     /* use floating point to avoid strangeness of integer division
17127        with negative dividends on many machines */
17128     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17129
17130     if (second < 0) {
17131         sign = "-";
17132         second = -second;
17133     }
17134
17135     day = second / (60 * 60 * 24);
17136     second = second % (60 * 60 * 24);
17137     hour = second / (60 * 60);
17138     second = second % (60 * 60);
17139     minute = second / 60;
17140     second = second % 60;
17141
17142     if (day > 0)
17143       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17144               sign, day, hour, minute, second);
17145     else if (hour > 0)
17146       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17147     else
17148       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17149
17150     return buf;
17151 }
17152
17153
17154 /*
17155  * This is necessary because some C libraries aren't ANSI C compliant yet.
17156  */
17157 char *
17158 StrStr (char *string, char *match)
17159 {
17160     int i, length;
17161
17162     length = strlen(match);
17163
17164     for (i = strlen(string) - length; i >= 0; i--, string++)
17165       if (!strncmp(match, string, length))
17166         return string;
17167
17168     return NULL;
17169 }
17170
17171 char *
17172 StrCaseStr (char *string, char *match)
17173 {
17174     int i, j, length;
17175
17176     length = strlen(match);
17177
17178     for (i = strlen(string) - length; i >= 0; i--, string++) {
17179         for (j = 0; j < length; j++) {
17180             if (ToLower(match[j]) != ToLower(string[j]))
17181               break;
17182         }
17183         if (j == length) return string;
17184     }
17185
17186     return NULL;
17187 }
17188
17189 #ifndef _amigados
17190 int
17191 StrCaseCmp (char *s1, char *s2)
17192 {
17193     char c1, c2;
17194
17195     for (;;) {
17196         c1 = ToLower(*s1++);
17197         c2 = ToLower(*s2++);
17198         if (c1 > c2) return 1;
17199         if (c1 < c2) return -1;
17200         if (c1 == NULLCHAR) return 0;
17201     }
17202 }
17203
17204
17205 int
17206 ToLower (int c)
17207 {
17208     return isupper(c) ? tolower(c) : c;
17209 }
17210
17211
17212 int
17213 ToUpper (int c)
17214 {
17215     return islower(c) ? toupper(c) : c;
17216 }
17217 #endif /* !_amigados    */
17218
17219 char *
17220 StrSave (char *s)
17221 {
17222   char *ret;
17223
17224   if ((ret = (char *) malloc(strlen(s) + 1)))
17225     {
17226       safeStrCpy(ret, s, strlen(s)+1);
17227     }
17228   return ret;
17229 }
17230
17231 char *
17232 StrSavePtr (char *s, char **savePtr)
17233 {
17234     if (*savePtr) {
17235         free(*savePtr);
17236     }
17237     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17238       safeStrCpy(*savePtr, s, strlen(s)+1);
17239     }
17240     return(*savePtr);
17241 }
17242
17243 char *
17244 PGNDate ()
17245 {
17246     time_t clock;
17247     struct tm *tm;
17248     char buf[MSG_SIZ];
17249
17250     clock = time((time_t *)NULL);
17251     tm = localtime(&clock);
17252     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17253             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17254     return StrSave(buf);
17255 }
17256
17257
17258 char *
17259 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17260 {
17261     int i, j, fromX, fromY, toX, toY;
17262     int whiteToPlay;
17263     char buf[MSG_SIZ];
17264     char *p, *q;
17265     int emptycount;
17266     ChessSquare piece;
17267
17268     whiteToPlay = (gameMode == EditPosition) ?
17269       !blackPlaysFirst : (move % 2 == 0);
17270     p = buf;
17271
17272     /* Piece placement data */
17273     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17274         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17275         emptycount = 0;
17276         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17277             if (boards[move][i][j] == EmptySquare) {
17278                 emptycount++;
17279             } else { ChessSquare piece = boards[move][i][j];
17280                 if (emptycount > 0) {
17281                     if(emptycount<10) /* [HGM] can be >= 10 */
17282                         *p++ = '0' + emptycount;
17283                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17284                     emptycount = 0;
17285                 }
17286                 if(PieceToChar(piece) == '+') {
17287                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17288                     *p++ = '+';
17289                     piece = (ChessSquare)(DEMOTED piece);
17290                 }
17291                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17292                 if(p[-1] == '~') {
17293                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17294                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17295                     *p++ = '~';
17296                 }
17297             }
17298         }
17299         if (emptycount > 0) {
17300             if(emptycount<10) /* [HGM] can be >= 10 */
17301                 *p++ = '0' + emptycount;
17302             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17303             emptycount = 0;
17304         }
17305         *p++ = '/';
17306     }
17307     *(p - 1) = ' ';
17308
17309     /* [HGM] print Crazyhouse or Shogi holdings */
17310     if( gameInfo.holdingsWidth ) {
17311         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17312         q = p;
17313         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17314             piece = boards[move][i][BOARD_WIDTH-1];
17315             if( piece != EmptySquare )
17316               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17317                   *p++ = PieceToChar(piece);
17318         }
17319         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17320             piece = boards[move][BOARD_HEIGHT-i-1][0];
17321             if( piece != EmptySquare )
17322               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17323                   *p++ = PieceToChar(piece);
17324         }
17325
17326         if( q == p ) *p++ = '-';
17327         *p++ = ']';
17328         *p++ = ' ';
17329     }
17330
17331     /* Active color */
17332     *p++ = whiteToPlay ? 'w' : 'b';
17333     *p++ = ' ';
17334
17335   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17336     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17337   } else {
17338   if(nrCastlingRights) {
17339      q = p;
17340      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17341        /* [HGM] write directly from rights */
17342            if(boards[move][CASTLING][2] != NoRights &&
17343               boards[move][CASTLING][0] != NoRights   )
17344                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17345            if(boards[move][CASTLING][2] != NoRights &&
17346               boards[move][CASTLING][1] != NoRights   )
17347                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17348            if(boards[move][CASTLING][5] != NoRights &&
17349               boards[move][CASTLING][3] != NoRights   )
17350                 *p++ = boards[move][CASTLING][3] + AAA;
17351            if(boards[move][CASTLING][5] != NoRights &&
17352               boards[move][CASTLING][4] != NoRights   )
17353                 *p++ = boards[move][CASTLING][4] + AAA;
17354      } else {
17355
17356         /* [HGM] write true castling rights */
17357         if( nrCastlingRights == 6 ) {
17358             int q, k=0;
17359             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17360                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17361             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17362                  boards[move][CASTLING][2] != NoRights  );
17363             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17364                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17365                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17366                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17367                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17368             }
17369             if(q) *p++ = 'Q';
17370             k = 0;
17371             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17372                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17373             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17374                  boards[move][CASTLING][5] != NoRights  );
17375             if(gameInfo.variant == VariantSChess) {
17376                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17377                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17378                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17379                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17380             }
17381             if(q) *p++ = 'q';
17382         }
17383      }
17384      if (q == p) *p++ = '-'; /* No castling rights */
17385      *p++ = ' ';
17386   }
17387
17388   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17389      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17390      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17391     /* En passant target square */
17392     if (move > backwardMostMove) {
17393         fromX = moveList[move - 1][0] - AAA;
17394         fromY = moveList[move - 1][1] - ONE;
17395         toX = moveList[move - 1][2] - AAA;
17396         toY = moveList[move - 1][3] - ONE;
17397         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17398             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17399             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17400             fromX == toX) {
17401             /* 2-square pawn move just happened */
17402             *p++ = toX + AAA;
17403             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17404         } else {
17405             *p++ = '-';
17406         }
17407     } else if(move == backwardMostMove) {
17408         // [HGM] perhaps we should always do it like this, and forget the above?
17409         if((signed char)boards[move][EP_STATUS] >= 0) {
17410             *p++ = boards[move][EP_STATUS] + AAA;
17411             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17412         } else {
17413             *p++ = '-';
17414         }
17415     } else {
17416         *p++ = '-';
17417     }
17418     *p++ = ' ';
17419   }
17420   }
17421
17422     if(moveCounts)
17423     {   int i = 0, j=move;
17424
17425         /* [HGM] find reversible plies */
17426         if (appData.debugMode) { int k;
17427             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17428             for(k=backwardMostMove; k<=forwardMostMove; k++)
17429                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17430
17431         }
17432
17433         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17434         if( j == backwardMostMove ) i += initialRulePlies;
17435         sprintf(p, "%d ", i);
17436         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17437
17438         /* Fullmove number */
17439         sprintf(p, "%d", (move / 2) + 1);
17440     } else *--p = NULLCHAR;
17441
17442     return StrSave(buf);
17443 }
17444
17445 Boolean
17446 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17447 {
17448     int i, j, k, w=0;
17449     char *p, c;
17450     int emptycount, virgin[BOARD_FILES];
17451     ChessSquare piece;
17452
17453     p = fen;
17454
17455     /* Piece placement data */
17456     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17457         j = 0;
17458         for (;;) {
17459             if (*p == '/' || *p == ' ' || *p == '[' ) {
17460                 if(j > w) w = j;
17461                 emptycount = gameInfo.boardWidth - j;
17462                 while (emptycount--)
17463                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17464                 if (*p == '/') p++;
17465                 else if(autoSize) { // we stumbled unexpectedly into end of board
17466                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17467                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17468                     }
17469                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17470                 }
17471                 break;
17472 #if(BOARD_FILES >= 10)
17473             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17474                 p++; emptycount=10;
17475                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17476                 while (emptycount--)
17477                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17478 #endif
17479             } else if (*p == '*') {
17480                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17481             } else if (isdigit(*p)) {
17482                 emptycount = *p++ - '0';
17483                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17484                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17485                 while (emptycount--)
17486                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17487             } else if (*p == '+' || isalpha(*p)) {
17488                 if (j >= gameInfo.boardWidth) return FALSE;
17489                 if(*p=='+') {
17490                     piece = CharToPiece(*++p);
17491                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17492                     piece = (ChessSquare) (PROMOTED piece ); p++;
17493                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17494                 } else piece = CharToPiece(*p++);
17495
17496                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17497                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17498                     piece = (ChessSquare) (PROMOTED piece);
17499                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17500                     p++;
17501                 }
17502                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17503             } else {
17504                 return FALSE;
17505             }
17506         }
17507     }
17508     while (*p == '/' || *p == ' ') p++;
17509
17510     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17511
17512     /* [HGM] by default clear Crazyhouse holdings, if present */
17513     if(gameInfo.holdingsWidth) {
17514        for(i=0; i<BOARD_HEIGHT; i++) {
17515            board[i][0]             = EmptySquare; /* black holdings */
17516            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17517            board[i][1]             = (ChessSquare) 0; /* black counts */
17518            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17519        }
17520     }
17521
17522     /* [HGM] look for Crazyhouse holdings here */
17523     while(*p==' ') p++;
17524     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17525         if(*p == '[') p++;
17526         if(*p == '-' ) p++; /* empty holdings */ else {
17527             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17528             /* if we would allow FEN reading to set board size, we would   */
17529             /* have to add holdings and shift the board read so far here   */
17530             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17531                 p++;
17532                 if((int) piece >= (int) BlackPawn ) {
17533                     i = (int)piece - (int)BlackPawn;
17534                     i = PieceToNumber((ChessSquare)i);
17535                     if( i >= gameInfo.holdingsSize ) return FALSE;
17536                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17537                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17538                 } else {
17539                     i = (int)piece - (int)WhitePawn;
17540                     i = PieceToNumber((ChessSquare)i);
17541                     if( i >= gameInfo.holdingsSize ) return FALSE;
17542                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17543                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17544                 }
17545             }
17546         }
17547         if(*p == ']') p++;
17548     }
17549
17550     while(*p == ' ') p++;
17551
17552     /* Active color */
17553     c = *p++;
17554     if(appData.colorNickNames) {
17555       if( c == appData.colorNickNames[0] ) c = 'w'; else
17556       if( c == appData.colorNickNames[1] ) c = 'b';
17557     }
17558     switch (c) {
17559       case 'w':
17560         *blackPlaysFirst = FALSE;
17561         break;
17562       case 'b':
17563         *blackPlaysFirst = TRUE;
17564         break;
17565       default:
17566         return FALSE;
17567     }
17568
17569     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17570     /* return the extra info in global variiables             */
17571
17572     /* set defaults in case FEN is incomplete */
17573     board[EP_STATUS] = EP_UNKNOWN;
17574     for(i=0; i<nrCastlingRights; i++ ) {
17575         board[CASTLING][i] =
17576             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17577     }   /* assume possible unless obviously impossible */
17578     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17579     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17580     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17581                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17582     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17583     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17584     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17585                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17586     FENrulePlies = 0;
17587
17588     while(*p==' ') p++;
17589     if(nrCastlingRights) {
17590       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17591       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17592           /* castling indicator present, so default becomes no castlings */
17593           for(i=0; i<nrCastlingRights; i++ ) {
17594                  board[CASTLING][i] = NoRights;
17595           }
17596       }
17597       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17598              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17599              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17600              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17601         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17602
17603         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17604             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17605             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17606         }
17607         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17608             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17609         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17610                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17611         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17612                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17613         switch(c) {
17614           case'K':
17615               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17616               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17617               board[CASTLING][2] = whiteKingFile;
17618               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17619               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17620               break;
17621           case'Q':
17622               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17623               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17624               board[CASTLING][2] = whiteKingFile;
17625               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17626               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17627               break;
17628           case'k':
17629               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17630               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17631               board[CASTLING][5] = blackKingFile;
17632               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17633               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17634               break;
17635           case'q':
17636               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17637               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17638               board[CASTLING][5] = blackKingFile;
17639               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17640               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17641           case '-':
17642               break;
17643           default: /* FRC castlings */
17644               if(c >= 'a') { /* black rights */
17645                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17646                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17647                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17648                   if(i == BOARD_RGHT) break;
17649                   board[CASTLING][5] = i;
17650                   c -= AAA;
17651                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17652                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17653                   if(c > i)
17654                       board[CASTLING][3] = c;
17655                   else
17656                       board[CASTLING][4] = c;
17657               } else { /* white rights */
17658                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17659                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17660                     if(board[0][i] == WhiteKing) break;
17661                   if(i == BOARD_RGHT) break;
17662                   board[CASTLING][2] = i;
17663                   c -= AAA - 'a' + 'A';
17664                   if(board[0][c] >= WhiteKing) break;
17665                   if(c > i)
17666                       board[CASTLING][0] = c;
17667                   else
17668                       board[CASTLING][1] = c;
17669               }
17670         }
17671       }
17672       for(i=0; i<nrCastlingRights; i++)
17673         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17674       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17675     if (appData.debugMode) {
17676         fprintf(debugFP, "FEN castling rights:");
17677         for(i=0; i<nrCastlingRights; i++)
17678         fprintf(debugFP, " %d", board[CASTLING][i]);
17679         fprintf(debugFP, "\n");
17680     }
17681
17682       while(*p==' ') p++;
17683     }
17684
17685     /* read e.p. field in games that know e.p. capture */
17686     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17687        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17688        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17689       if(*p=='-') {
17690         p++; board[EP_STATUS] = EP_NONE;
17691       } else {
17692          char c = *p++ - AAA;
17693
17694          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17695          if(*p >= '0' && *p <='9') p++;
17696          board[EP_STATUS] = c;
17697       }
17698     }
17699
17700
17701     if(sscanf(p, "%d", &i) == 1) {
17702         FENrulePlies = i; /* 50-move ply counter */
17703         /* (The move number is still ignored)    */
17704     }
17705
17706     return TRUE;
17707 }
17708
17709 void
17710 EditPositionPasteFEN (char *fen)
17711 {
17712   if (fen != NULL) {
17713     Board initial_position;
17714
17715     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17716       DisplayError(_("Bad FEN position in clipboard"), 0);
17717       return ;
17718     } else {
17719       int savedBlackPlaysFirst = blackPlaysFirst;
17720       EditPositionEvent();
17721       blackPlaysFirst = savedBlackPlaysFirst;
17722       CopyBoard(boards[0], initial_position);
17723       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17724       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17725       DisplayBothClocks();
17726       DrawPosition(FALSE, boards[currentMove]);
17727     }
17728   }
17729 }
17730
17731 static char cseq[12] = "\\   ";
17732
17733 Boolean
17734 set_cont_sequence (char *new_seq)
17735 {
17736     int len;
17737     Boolean ret;
17738
17739     // handle bad attempts to set the sequence
17740         if (!new_seq)
17741                 return 0; // acceptable error - no debug
17742
17743     len = strlen(new_seq);
17744     ret = (len > 0) && (len < sizeof(cseq));
17745     if (ret)
17746       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17747     else if (appData.debugMode)
17748       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17749     return ret;
17750 }
17751
17752 /*
17753     reformat a source message so words don't cross the width boundary.  internal
17754     newlines are not removed.  returns the wrapped size (no null character unless
17755     included in source message).  If dest is NULL, only calculate the size required
17756     for the dest buffer.  lp argument indicats line position upon entry, and it's
17757     passed back upon exit.
17758 */
17759 int
17760 wrap (char *dest, char *src, int count, int width, int *lp)
17761 {
17762     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17763
17764     cseq_len = strlen(cseq);
17765     old_line = line = *lp;
17766     ansi = len = clen = 0;
17767
17768     for (i=0; i < count; i++)
17769     {
17770         if (src[i] == '\033')
17771             ansi = 1;
17772
17773         // if we hit the width, back up
17774         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17775         {
17776             // store i & len in case the word is too long
17777             old_i = i, old_len = len;
17778
17779             // find the end of the last word
17780             while (i && src[i] != ' ' && src[i] != '\n')
17781             {
17782                 i--;
17783                 len--;
17784             }
17785
17786             // word too long?  restore i & len before splitting it
17787             if ((old_i-i+clen) >= width)
17788             {
17789                 i = old_i;
17790                 len = old_len;
17791             }
17792
17793             // extra space?
17794             if (i && src[i-1] == ' ')
17795                 len--;
17796
17797             if (src[i] != ' ' && src[i] != '\n')
17798             {
17799                 i--;
17800                 if (len)
17801                     len--;
17802             }
17803
17804             // now append the newline and continuation sequence
17805             if (dest)
17806                 dest[len] = '\n';
17807             len++;
17808             if (dest)
17809                 strncpy(dest+len, cseq, cseq_len);
17810             len += cseq_len;
17811             line = cseq_len;
17812             clen = cseq_len;
17813             continue;
17814         }
17815
17816         if (dest)
17817             dest[len] = src[i];
17818         len++;
17819         if (!ansi)
17820             line++;
17821         if (src[i] == '\n')
17822             line = 0;
17823         if (src[i] == 'm')
17824             ansi = 0;
17825     }
17826     if (dest && appData.debugMode)
17827     {
17828         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17829             count, width, line, len, *lp);
17830         show_bytes(debugFP, src, count);
17831         fprintf(debugFP, "\ndest: ");
17832         show_bytes(debugFP, dest, len);
17833         fprintf(debugFP, "\n");
17834     }
17835     *lp = dest ? line : old_line;
17836
17837     return len;
17838 }
17839
17840 // [HGM] vari: routines for shelving variations
17841 Boolean modeRestore = FALSE;
17842
17843 void
17844 PushInner (int firstMove, int lastMove)
17845 {
17846         int i, j, nrMoves = lastMove - firstMove;
17847
17848         // push current tail of game on stack
17849         savedResult[storedGames] = gameInfo.result;
17850         savedDetails[storedGames] = gameInfo.resultDetails;
17851         gameInfo.resultDetails = NULL;
17852         savedFirst[storedGames] = firstMove;
17853         savedLast [storedGames] = lastMove;
17854         savedFramePtr[storedGames] = framePtr;
17855         framePtr -= nrMoves; // reserve space for the boards
17856         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17857             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17858             for(j=0; j<MOVE_LEN; j++)
17859                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17860             for(j=0; j<2*MOVE_LEN; j++)
17861                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17862             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17863             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17864             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17865             pvInfoList[firstMove+i-1].depth = 0;
17866             commentList[framePtr+i] = commentList[firstMove+i];
17867             commentList[firstMove+i] = NULL;
17868         }
17869
17870         storedGames++;
17871         forwardMostMove = firstMove; // truncate game so we can start variation
17872 }
17873
17874 void
17875 PushTail (int firstMove, int lastMove)
17876 {
17877         if(appData.icsActive) { // only in local mode
17878                 forwardMostMove = currentMove; // mimic old ICS behavior
17879                 return;
17880         }
17881         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17882
17883         PushInner(firstMove, lastMove);
17884         if(storedGames == 1) GreyRevert(FALSE);
17885         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17886 }
17887
17888 void
17889 PopInner (Boolean annotate)
17890 {
17891         int i, j, nrMoves;
17892         char buf[8000], moveBuf[20];
17893
17894         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17895         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17896         nrMoves = savedLast[storedGames] - currentMove;
17897         if(annotate) {
17898                 int cnt = 10;
17899                 if(!WhiteOnMove(currentMove))
17900                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17901                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17902                 for(i=currentMove; i<forwardMostMove; i++) {
17903                         if(WhiteOnMove(i))
17904                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17905                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17906                         strcat(buf, moveBuf);
17907                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17908                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17909                 }
17910                 strcat(buf, ")");
17911         }
17912         for(i=1; i<=nrMoves; i++) { // copy last variation back
17913             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17914             for(j=0; j<MOVE_LEN; j++)
17915                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17916             for(j=0; j<2*MOVE_LEN; j++)
17917                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17918             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17919             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17920             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17921             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17922             commentList[currentMove+i] = commentList[framePtr+i];
17923             commentList[framePtr+i] = NULL;
17924         }
17925         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17926         framePtr = savedFramePtr[storedGames];
17927         gameInfo.result = savedResult[storedGames];
17928         if(gameInfo.resultDetails != NULL) {
17929             free(gameInfo.resultDetails);
17930       }
17931         gameInfo.resultDetails = savedDetails[storedGames];
17932         forwardMostMove = currentMove + nrMoves;
17933 }
17934
17935 Boolean
17936 PopTail (Boolean annotate)
17937 {
17938         if(appData.icsActive) return FALSE; // only in local mode
17939         if(!storedGames) return FALSE; // sanity
17940         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17941
17942         PopInner(annotate);
17943         if(currentMove < forwardMostMove) ForwardEvent(); else
17944         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17945
17946         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17947         return TRUE;
17948 }
17949
17950 void
17951 CleanupTail ()
17952 {       // remove all shelved variations
17953         int i;
17954         for(i=0; i<storedGames; i++) {
17955             if(savedDetails[i])
17956                 free(savedDetails[i]);
17957             savedDetails[i] = NULL;
17958         }
17959         for(i=framePtr; i<MAX_MOVES; i++) {
17960                 if(commentList[i]) free(commentList[i]);
17961                 commentList[i] = NULL;
17962         }
17963         framePtr = MAX_MOVES-1;
17964         storedGames = 0;
17965 }
17966
17967 void
17968 LoadVariation (int index, char *text)
17969 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17970         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17971         int level = 0, move;
17972
17973         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17974         // first find outermost bracketing variation
17975         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17976             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17977                 if(*p == '{') wait = '}'; else
17978                 if(*p == '[') wait = ']'; else
17979                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17980                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17981             }
17982             if(*p == wait) wait = NULLCHAR; // closing ]} found
17983             p++;
17984         }
17985         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17986         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17987         end[1] = NULLCHAR; // clip off comment beyond variation
17988         ToNrEvent(currentMove-1);
17989         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17990         // kludge: use ParsePV() to append variation to game
17991         move = currentMove;
17992         ParsePV(start, TRUE, TRUE);
17993         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17994         ClearPremoveHighlights();
17995         CommentPopDown();
17996         ToNrEvent(currentMove+1);
17997 }
17998
17999 void
18000 LoadTheme ()
18001 {
18002     char *p, *q, buf[MSG_SIZ];
18003     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18004         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18005         ParseArgsFromString(buf);
18006         ActivateTheme(TRUE); // also redo colors
18007         return;
18008     }
18009     p = nickName;
18010     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18011     {
18012         int len;
18013         q = appData.themeNames;
18014         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18015       if(appData.useBitmaps) {
18016         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18017                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18018                 appData.liteBackTextureMode,
18019                 appData.darkBackTextureMode );
18020       } else {
18021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18022                 Col2Text(2),   // lightSquareColor
18023                 Col2Text(3) ); // darkSquareColor
18024       }
18025       if(appData.useBorder) {
18026         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18027                 appData.border);
18028       } else {
18029         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18030       }
18031       if(appData.useFont) {
18032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18033                 appData.renderPiecesWithFont,
18034                 appData.fontToPieceTable,
18035                 Col2Text(9),    // appData.fontBackColorWhite
18036                 Col2Text(10) ); // appData.fontForeColorBlack
18037       } else {
18038         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18039                 appData.pieceDirectory);
18040         if(!appData.pieceDirectory[0])
18041           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18042                 Col2Text(0),   // whitePieceColor
18043                 Col2Text(1) ); // blackPieceColor
18044       }
18045       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18046                 Col2Text(4),   // highlightSquareColor
18047                 Col2Text(5) ); // premoveHighlightColor
18048         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18049         if(insert != q) insert[-1] = NULLCHAR;
18050         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18051         if(q)   free(q);
18052     }
18053     ActivateTheme(FALSE);
18054 }