46e888e4630a02261423bc1d4118d2fc55667619
[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 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
652
653
654 Board initialPosition;
655
656
657 /* Convert str to a rating. Checks for special cases of "----",
658
659    "++++", etc. Also strips ()'s */
660 int
661 string_to_rating (char *str)
662 {
663   while(*str && !isdigit(*str)) ++str;
664   if (!*str)
665     return 0;   /* One of the special "no rating" cases */
666   else
667     return atoi(str);
668 }
669
670 void
671 ClearProgramStats ()
672 {
673     /* Init programStats */
674     programStats.movelist[0] = 0;
675     programStats.depth = 0;
676     programStats.nr_moves = 0;
677     programStats.moves_left = 0;
678     programStats.nodes = 0;
679     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
680     programStats.score = 0;
681     programStats.got_only_move = 0;
682     programStats.got_fail = 0;
683     programStats.line_is_book = 0;
684 }
685
686 void
687 CommonEngineInit ()
688 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689     if (appData.firstPlaysBlack) {
690         first.twoMachinesColor = "black\n";
691         second.twoMachinesColor = "white\n";
692     } else {
693         first.twoMachinesColor = "white\n";
694         second.twoMachinesColor = "black\n";
695     }
696
697     first.other = &second;
698     second.other = &first;
699
700     { float norm = 1;
701         if(appData.timeOddsMode) {
702             norm = appData.timeOdds[0];
703             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704         }
705         first.timeOdds  = appData.timeOdds[0]/norm;
706         second.timeOdds = appData.timeOdds[1]/norm;
707     }
708
709     if(programVersion) free(programVersion);
710     if (appData.noChessProgram) {
711         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712         sprintf(programVersion, "%s", PACKAGE_STRING);
713     } else {
714       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
717     }
718 }
719
720 void
721 UnloadEngine (ChessProgramState *cps)
722 {
723         /* Kill off first chess program */
724         if (cps->isr != NULL)
725           RemoveInputSource(cps->isr);
726         cps->isr = NULL;
727
728         if (cps->pr != NoProc) {
729             ExitAnalyzeMode();
730             DoSleep( appData.delayBeforeQuit );
731             SendToProgram("quit\n", cps);
732             DoSleep( appData.delayAfterQuit );
733             DestroyChildProcess(cps->pr, cps->useSigterm);
734         }
735         cps->pr = NoProc;
736         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
737 }
738
739 void
740 ClearOptions (ChessProgramState *cps)
741 {
742     int i;
743     cps->nrOptions = cps->comboCnt = 0;
744     for(i=0; i<MAX_OPTIONS; i++) {
745         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746         cps->option[i].textValue = 0;
747     }
748 }
749
750 char *engineNames[] = {
751   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
753 N_("first"),
754   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
756 N_("second")
757 };
758
759 void
760 InitEngine (ChessProgramState *cps, int n)
761 {   // [HGM] all engine initialiation put in a function that does one engine
762
763     ClearOptions(cps);
764
765     cps->which = engineNames[n];
766     cps->maybeThinking = FALSE;
767     cps->pr = NoProc;
768     cps->isr = NULL;
769     cps->sendTime = 2;
770     cps->sendDrawOffers = 1;
771
772     cps->program = appData.chessProgram[n];
773     cps->host = appData.host[n];
774     cps->dir = appData.directory[n];
775     cps->initString = appData.engInitString[n];
776     cps->computerString = appData.computerString[n];
777     cps->useSigint  = TRUE;
778     cps->useSigterm = TRUE;
779     cps->reuse = appData.reuse[n];
780     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
781     cps->useSetboard = FALSE;
782     cps->useSAN = FALSE;
783     cps->usePing = FALSE;
784     cps->lastPing = 0;
785     cps->lastPong = 0;
786     cps->usePlayother = FALSE;
787     cps->useColors = TRUE;
788     cps->useUsermove = FALSE;
789     cps->sendICS = FALSE;
790     cps->sendName = appData.icsActive;
791     cps->sdKludge = FALSE;
792     cps->stKludge = FALSE;
793     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794     TidyProgramName(cps->program, cps->host, cps->tidy);
795     cps->matchWins = 0;
796     ASSIGN(cps->variants, appData.variant);
797     cps->analysisSupport = 2; /* detect */
798     cps->analyzing = FALSE;
799     cps->initDone = FALSE;
800     cps->reload = FALSE;
801
802     /* New features added by Tord: */
803     cps->useFEN960 = FALSE;
804     cps->useOOCastle = TRUE;
805     /* End of new features added by Tord. */
806     cps->fenOverride  = appData.fenOverride[n];
807
808     /* [HGM] time odds: set factor for each machine */
809     cps->timeOdds  = appData.timeOdds[n];
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     cps->accumulateTC = appData.accumulateTC[n];
813     cps->maxNrOfSessions = 1;
814
815     /* [HGM] debug */
816     cps->debug = FALSE;
817
818     cps->supportsNPS = UNKNOWN;
819     cps->memSize = FALSE;
820     cps->maxCores = FALSE;
821     ASSIGN(cps->egtFormats, "");
822
823     /* [HGM] options */
824     cps->optionSettings  = appData.engOptions[n];
825
826     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827     cps->isUCI = appData.isUCI[n]; /* [AS] */
828     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
829     cps->highlight = 0;
830
831     if (appData.protocolVersion[n] > PROTOVER
832         || appData.protocolVersion[n] < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.protocolVersion[n]);
839         if( (len >= MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         cps->protocolVersion = appData.protocolVersion[n];
847       }
848
849     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
850     ParseFeatures(appData.featureDefaults, cps);
851 }
852
853 ChessProgramState *savCps;
854
855 GameMode oldMode;
856
857 void
858 LoadEngine ()
859 {
860     int i;
861     if(WaitForEngine(savCps, LoadEngine)) return;
862     CommonEngineInit(); // recalculate time odds
863     if(gameInfo.variant != StringToVariant(appData.variant)) {
864         // we changed variant when loading the engine; this forces us to reset
865         Reset(TRUE, savCps != &first);
866         oldMode = BeginningOfGame; // to prevent restoring old mode
867     }
868     InitChessProgram(savCps, FALSE);
869     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870     DisplayMessage("", "");
871     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
873     ThawUI();
874     SetGNUMode();
875     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
876 }
877
878 void
879 ReplaceEngine (ChessProgramState *cps, int n)
880 {
881     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
882     keepInfo = 1;
883     if(oldMode != BeginningOfGame) EditGameEvent();
884     keepInfo = 0;
885     UnloadEngine(cps);
886     appData.noChessProgram = FALSE;
887     appData.clockMode = TRUE;
888     InitEngine(cps, n);
889     UpdateLogos(TRUE);
890     if(n) return; // only startup first engine immediately; second can wait
891     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
892     LoadEngine();
893 }
894
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
897
898 static char resetOptions[] =
899         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
903
904 void
905 FloatToFront(char **list, char *engineLine)
906 {
907     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
908     int i=0;
909     if(appData.recentEngines <= 0) return;
910     TidyProgramName(engineLine, "localhost", tidy+1);
911     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912     strncpy(buf+1, *list, MSG_SIZ-50);
913     if(p = strstr(buf, tidy)) { // tidy name appears in list
914         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915         while(*p++ = *++q); // squeeze out
916     }
917     strcat(tidy, buf+1); // put list behind tidy name
918     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920     ASSIGN(*list, tidy+1);
921 }
922
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
924
925 void
926 Load (ChessProgramState *cps, int i)
927 {
928     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934         appData.firstProtocolVersion = PROTOVER;
935         ParseArgsFromString(buf);
936         SwapEngines(i);
937         ReplaceEngine(cps, i);
938         FloatToFront(&appData.recentEngineList, engineLine);
939         return;
940     }
941     p = engineName;
942     while(q = strchr(p, SLASH)) p = q+1;
943     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944     if(engineDir[0] != NULLCHAR) {
945         ASSIGN(appData.directory[i], engineDir); p = engineName;
946     } else if(p != engineName) { // derive directory from engine path, when not given
947         p[-1] = 0;
948         ASSIGN(appData.directory[i], engineName);
949         p[-1] = SLASH;
950         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951     } else { ASSIGN(appData.directory[i], "."); }
952     if(params[0]) {
953         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954         snprintf(command, MSG_SIZ, "%s %s", p, params);
955         p = command;
956     }
957     ASSIGN(appData.chessProgram[i], p);
958     appData.isUCI[i] = isUCI;
959     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960     appData.hasOwnBookUCI[i] = hasBook;
961     if(!nickName[0]) useNick = FALSE;
962     if(useNick) ASSIGN(appData.pgnName[i], nickName);
963     if(addToList) {
964         int len;
965         char quote;
966         q = firstChessProgramNames;
967         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970                         quote, p, quote, appData.directory[i],
971                         useNick ? " -fn \"" : "",
972                         useNick ? nickName : "",
973                         useNick ? "\"" : "",
974                         v1 ? " -firstProtocolVersion 1" : "",
975                         hasBook ? "" : " -fNoOwnBookUCI",
976                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977                         storeVariant ? " -variant " : "",
978                         storeVariant ? VariantName(gameInfo.variant) : "");
979         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981         if(insert != q) insert[-1] = NULLCHAR;
982         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
983         if(q)   free(q);
984         FloatToFront(&appData.recentEngineList, buf);
985     }
986     ReplaceEngine(cps, i);
987 }
988
989 void
990 InitTimeControls ()
991 {
992     int matched, min, sec;
993     /*
994      * Parse timeControl resource
995      */
996     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997                           appData.movesPerSession)) {
998         char buf[MSG_SIZ];
999         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000         DisplayFatalError(buf, 0, 2);
1001     }
1002
1003     /*
1004      * Parse searchTime resource
1005      */
1006     if (*appData.searchTime != NULLCHAR) {
1007         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1008         if (matched == 1) {
1009             searchTime = min * 60;
1010         } else if (matched == 2) {
1011             searchTime = min * 60 + sec;
1012         } else {
1013             char buf[MSG_SIZ];
1014             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015             DisplayFatalError(buf, 0, 2);
1016         }
1017     }
1018 }
1019
1020 void
1021 InitBackEnd1 ()
1022 {
1023
1024     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1026
1027     GetTimeMark(&programStartTime);
1028     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029     appData.seedBase = random() + (random()<<15);
1030     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1031
1032     ClearProgramStats();
1033     programStats.ok_to_send = 1;
1034     programStats.seen_stat = 0;
1035
1036     /*
1037      * Initialize game list
1038      */
1039     ListNew(&gameList);
1040
1041
1042     /*
1043      * Internet chess server status
1044      */
1045     if (appData.icsActive) {
1046         appData.matchMode = FALSE;
1047         appData.matchGames = 0;
1048 #if ZIPPY
1049         appData.noChessProgram = !appData.zippyPlay;
1050 #else
1051         appData.zippyPlay = FALSE;
1052         appData.zippyTalk = FALSE;
1053         appData.noChessProgram = TRUE;
1054 #endif
1055         if (*appData.icsHelper != NULLCHAR) {
1056             appData.useTelnet = TRUE;
1057             appData.telnetProgram = appData.icsHelper;
1058         }
1059     } else {
1060         appData.zippyTalk = appData.zippyPlay = FALSE;
1061     }
1062
1063     /* [AS] Initialize pv info list [HGM] and game state */
1064     {
1065         int i, j;
1066
1067         for( i=0; i<=framePtr; i++ ) {
1068             pvInfoList[i].depth = -1;
1069             boards[i][EP_STATUS] = EP_NONE;
1070             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1071         }
1072     }
1073
1074     InitTimeControls();
1075
1076     /* [AS] Adjudication threshold */
1077     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1078
1079     InitEngine(&first, 0);
1080     InitEngine(&second, 1);
1081     CommonEngineInit();
1082
1083     pairing.which = "pairing"; // pairing engine
1084     pairing.pr = NoProc;
1085     pairing.isr = NULL;
1086     pairing.program = appData.pairingEngine;
1087     pairing.host = "localhost";
1088     pairing.dir = ".";
1089
1090     if (appData.icsActive) {
1091         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1092     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093         appData.clockMode = FALSE;
1094         first.sendTime = second.sendTime = 0;
1095     }
1096
1097 #if ZIPPY
1098     /* Override some settings from environment variables, for backward
1099        compatibility.  Unfortunately it's not feasible to have the env
1100        vars just set defaults, at least in xboard.  Ugh.
1101     */
1102     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1103       ZippyInit();
1104     }
1105 #endif
1106
1107     if (!appData.icsActive) {
1108       char buf[MSG_SIZ];
1109       int len;
1110
1111       /* Check for variants that are supported only in ICS mode,
1112          or not at all.  Some that are accepted here nevertheless
1113          have bugs; see comments below.
1114       */
1115       VariantClass variant = StringToVariant(appData.variant);
1116       switch (variant) {
1117       case VariantBughouse:     /* need four players and two boards */
1118       case VariantKriegspiel:   /* need to hide pieces and move details */
1119         /* case VariantFischeRandom: (Fabien: moved below) */
1120         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121         if( (len >= MSG_SIZ) && appData.debugMode )
1122           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1123
1124         DisplayFatalError(buf, 0, 2);
1125         return;
1126
1127       case VariantUnknown:
1128       case VariantLoadable:
1129       case Variant29:
1130       case Variant30:
1131       case Variant31:
1132       case Variant32:
1133       case Variant33:
1134       case Variant34:
1135       case Variant35:
1136       case Variant36:
1137       default:
1138         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139         if( (len >= MSG_SIZ) && appData.debugMode )
1140           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1141
1142         DisplayFatalError(buf, 0, 2);
1143         return;
1144
1145       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1146       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1147       case VariantGothic:     /* [HGM] should work */
1148       case VariantCapablanca: /* [HGM] should work */
1149       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1150       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1151       case VariantKnightmate: /* [HGM] should work */
1152       case VariantCylinder:   /* [HGM] untested */
1153       case VariantFalcon:     /* [HGM] untested */
1154       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155                                  offboard interposition not understood */
1156       case VariantNormal:     /* definitely works! */
1157       case VariantWildCastle: /* pieces not automatically shuffled */
1158       case VariantNoCastle:   /* pieces not automatically shuffled */
1159       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160       case VariantLosers:     /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantSuicide:    /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantGiveaway:   /* should work except for win condition,
1165                                  and doesn't know captures are mandatory */
1166       case VariantTwoKings:   /* should work */
1167       case VariantAtomic:     /* should work except for win condition */
1168       case Variant3Check:     /* should work except for win condition */
1169       case VariantShatranj:   /* should work except for all win conditions */
1170       case VariantMakruk:     /* should work except for draw countdown */
1171       case VariantASEAN :     /* should work except for draw countdown */
1172       case VariantBerolina:   /* might work if TestLegality is off */
1173       case VariantCapaRandom: /* should work */
1174       case VariantJanus:      /* should work */
1175       case VariantSuper:      /* experimental */
1176       case VariantGreat:      /* experimental, requires legality testing to be off */
1177       case VariantSChess:     /* S-Chess, should work */
1178       case VariantGrand:      /* should work */
1179       case VariantSpartan:    /* should work */
1180         break;
1181       }
1182     }
1183
1184 }
1185
1186 int
1187 NextIntegerFromString (char ** str, long * value)
1188 {
1189     int result = -1;
1190     char * s = *str;
1191
1192     while( *s == ' ' || *s == '\t' ) {
1193         s++;
1194     }
1195
1196     *value = 0;
1197
1198     if( *s >= '0' && *s <= '9' ) {
1199         while( *s >= '0' && *s <= '9' ) {
1200             *value = *value * 10 + (*s - '0');
1201             s++;
1202         }
1203
1204         result = 0;
1205     }
1206
1207     *str = s;
1208
1209     return result;
1210 }
1211
1212 int
1213 NextTimeControlFromString (char ** str, long * value)
1214 {
1215     long temp;
1216     int result = NextIntegerFromString( str, &temp );
1217
1218     if( result == 0 ) {
1219         *value = temp * 60; /* Minutes */
1220         if( **str == ':' ) {
1221             (*str)++;
1222             result = NextIntegerFromString( str, &temp );
1223             *value += temp; /* Seconds */
1224         }
1225     }
1226
1227     return result;
1228 }
1229
1230 int
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233     int result = -1, type = 0; long temp, temp2;
1234
1235     if(**str != ':') return -1; // old params remain in force!
1236     (*str)++;
1237     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238     if( NextIntegerFromString( str, &temp ) ) return -1;
1239     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1240
1241     if(**str != '/') {
1242         /* time only: incremental or sudden-death time control */
1243         if(**str == '+') { /* increment follows; read it */
1244             (*str)++;
1245             if(**str == '!') type = *(*str)++; // Bronstein TC
1246             if(result = NextIntegerFromString( str, &temp2)) return -1;
1247             *inc = temp2 * 1000;
1248             if(**str == '.') { // read fraction of increment
1249                 char *start = ++(*str);
1250                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1251                 temp2 *= 1000;
1252                 while(start++ < *str) temp2 /= 10;
1253                 *inc += temp2;
1254             }
1255         } else *inc = 0;
1256         *moves = 0; *tc = temp * 1000; *incType = type;
1257         return 0;
1258     }
1259
1260     (*str)++; /* classical time control */
1261     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1262
1263     if(result == 0) {
1264         *moves = temp;
1265         *tc    = temp2 * 1000;
1266         *inc   = 0;
1267         *incType = type;
1268     }
1269     return result;
1270 }
1271
1272 int
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 {   /* [HGM] get time to add from the multi-session time-control string */
1275     int incType, moves=1; /* kludge to force reading of first session */
1276     long time, increment;
1277     char *s = tcString;
1278
1279     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1280     do {
1281         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283         if(movenr == -1) return time;    /* last move before new session     */
1284         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286         if(!moves) return increment;     /* current session is incremental   */
1287         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288     } while(movenr >= -1);               /* try again for next session       */
1289
1290     return 0; // no new time quota on this move
1291 }
1292
1293 int
1294 ParseTimeControl (char *tc, float ti, int mps)
1295 {
1296   long tc1;
1297   long tc2;
1298   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1299   int min, sec=0;
1300
1301   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1304   if(ti > 0) {
1305
1306     if(mps)
1307       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1308     else
1309       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1310   } else {
1311     if(mps)
1312       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1313     else
1314       snprintf(buf, MSG_SIZ, ":%s", mytc);
1315   }
1316   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1317
1318   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1319     return FALSE;
1320   }
1321
1322   if( *tc == '/' ) {
1323     /* Parse second time control */
1324     tc++;
1325
1326     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1327       return FALSE;
1328     }
1329
1330     if( tc2 == 0 ) {
1331       return FALSE;
1332     }
1333
1334     timeControl_2 = tc2 * 1000;
1335   }
1336   else {
1337     timeControl_2 = 0;
1338   }
1339
1340   if( tc1 == 0 ) {
1341     return FALSE;
1342   }
1343
1344   timeControl = tc1 * 1000;
1345
1346   if (ti >= 0) {
1347     timeIncrement = ti * 1000;  /* convert to ms */
1348     movesPerSession = 0;
1349   } else {
1350     timeIncrement = 0;
1351     movesPerSession = mps;
1352   }
1353   return TRUE;
1354 }
1355
1356 void
1357 InitBackEnd2 ()
1358 {
1359     if (appData.debugMode) {
1360 #    ifdef __GIT_VERSION
1361       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1362 #    else
1363       fprintf(debugFP, "Version: %s\n", programVersion);
1364 #    endif
1365     }
1366     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1367
1368     set_cont_sequence(appData.wrapContSeq);
1369     if (appData.matchGames > 0) {
1370         appData.matchMode = TRUE;
1371     } else if (appData.matchMode) {
1372         appData.matchGames = 1;
1373     }
1374     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375         appData.matchGames = appData.sameColorGames;
1376     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1379     }
1380     Reset(TRUE, FALSE);
1381     if (appData.noChessProgram || first.protocolVersion == 1) {
1382       InitBackEnd3();
1383     } else {
1384       /* kludge: allow timeout for initial "feature" commands */
1385       FreezeUI();
1386       DisplayMessage("", _("Starting chess program"));
1387       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1388     }
1389 }
1390
1391 int
1392 CalculateIndex (int index, int gameNr)
1393 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1394     int res;
1395     if(index > 0) return index; // fixed nmber
1396     if(index == 0) return 1;
1397     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1399     return res;
1400 }
1401
1402 int
1403 LoadGameOrPosition (int gameNr)
1404 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405     if (*appData.loadGameFile != NULLCHAR) {
1406         if (!LoadGameFromFile(appData.loadGameFile,
1407                 CalculateIndex(appData.loadGameIndex, gameNr),
1408                               appData.loadGameFile, FALSE)) {
1409             DisplayFatalError(_("Bad game file"), 0, 1);
1410             return 0;
1411         }
1412     } else if (*appData.loadPositionFile != NULLCHAR) {
1413         if (!LoadPositionFromFile(appData.loadPositionFile,
1414                 CalculateIndex(appData.loadPositionIndex, gameNr),
1415                                   appData.loadPositionFile)) {
1416             DisplayFatalError(_("Bad position file"), 0, 1);
1417             return 0;
1418         }
1419     }
1420     return 1;
1421 }
1422
1423 void
1424 ReserveGame (int gameNr, char resChar)
1425 {
1426     FILE *tf = fopen(appData.tourneyFile, "r+");
1427     char *p, *q, c, buf[MSG_SIZ];
1428     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429     safeStrCpy(buf, lastMsg, MSG_SIZ);
1430     DisplayMessage(_("Pick new game"), "");
1431     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432     ParseArgsFromFile(tf);
1433     p = q = appData.results;
1434     if(appData.debugMode) {
1435       char *r = appData.participants;
1436       fprintf(debugFP, "results = '%s'\n", p);
1437       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438       fprintf(debugFP, "\n");
1439     }
1440     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1441     nextGame = q - p;
1442     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443     safeStrCpy(q, p, strlen(p) + 2);
1444     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1448         q[nextGame] = '*';
1449     }
1450     fseek(tf, -(strlen(p)+4), SEEK_END);
1451     c = fgetc(tf);
1452     if(c != '"') // depending on DOS or Unix line endings we can be one off
1453          fseek(tf, -(strlen(p)+2), SEEK_END);
1454     else fseek(tf, -(strlen(p)+3), SEEK_END);
1455     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456     DisplayMessage(buf, "");
1457     free(p); appData.results = q;
1458     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460       int round = appData.defaultMatchGames * appData.tourneyType;
1461       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1462          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463         UnloadEngine(&first);  // next game belongs to other pairing;
1464         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1465     }
1466     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1467 }
1468
1469 void
1470 MatchEvent (int mode)
1471 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1472         int dummy;
1473         if(matchMode) { // already in match mode: switch it off
1474             abortMatch = TRUE;
1475             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1476             return;
1477         }
1478 //      if(gameMode != BeginningOfGame) {
1479 //          DisplayError(_("You can only start a match from the initial position."), 0);
1480 //          return;
1481 //      }
1482         abortMatch = FALSE;
1483         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484         /* Set up machine vs. machine match */
1485         nextGame = 0;
1486         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487         if(appData.tourneyFile[0]) {
1488             ReserveGame(-1, 0);
1489             if(nextGame > appData.matchGames) {
1490                 char buf[MSG_SIZ];
1491                 if(strchr(appData.results, '*') == NULL) {
1492                     FILE *f;
1493                     appData.tourneyCycles++;
1494                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1495                         fclose(f);
1496                         NextTourneyGame(-1, &dummy);
1497                         ReserveGame(-1, 0);
1498                         if(nextGame <= appData.matchGames) {
1499                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1500                             matchMode = mode;
1501                             ScheduleDelayedEvent(NextMatchGame, 10000);
1502                             return;
1503                         }
1504                     }
1505                 }
1506                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507                 DisplayError(buf, 0);
1508                 appData.tourneyFile[0] = 0;
1509                 return;
1510             }
1511         } else
1512         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1513             DisplayFatalError(_("Can't have a match with no chess programs"),
1514                               0, 2);
1515             return;
1516         }
1517         matchMode = mode;
1518         matchGame = roundNr = 1;
1519         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1520         NextMatchGame();
1521 }
1522
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1524
1525 void
1526 InitBackEnd3 P((void))
1527 {
1528     GameMode initialMode;
1529     char buf[MSG_SIZ];
1530     int err, len;
1531
1532     InitChessProgram(&first, startedFromSetupPosition);
1533
1534     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1535         free(programVersion);
1536         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1539     }
1540
1541     if (appData.icsActive) {
1542 #ifdef WIN32
1543         /* [DM] Make a console window if needed [HGM] merged ifs */
1544         ConsoleCreate();
1545 #endif
1546         err = establish();
1547         if (err != 0)
1548           {
1549             if (*appData.icsCommPort != NULLCHAR)
1550               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551                              appData.icsCommPort);
1552             else
1553               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554                         appData.icsHost, appData.icsPort);
1555
1556             if( (len >= MSG_SIZ) && appData.debugMode )
1557               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1558
1559             DisplayFatalError(buf, err, 1);
1560             return;
1561         }
1562         SetICSMode();
1563         telnetISR =
1564           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1565         fromUserISR =
1566           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569     } else if (appData.noChessProgram) {
1570         SetNCPMode();
1571     } else {
1572         SetGNUMode();
1573     }
1574
1575     if (*appData.cmailGameName != NULLCHAR) {
1576         SetCmailMode();
1577         OpenLoopback(&cmailPR);
1578         cmailISR =
1579           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1580     }
1581
1582     ThawUI();
1583     DisplayMessage("", "");
1584     if (StrCaseCmp(appData.initialMode, "") == 0) {
1585       initialMode = BeginningOfGame;
1586       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1590         ModeHighlight();
1591       }
1592     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593       initialMode = TwoMachinesPlay;
1594     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595       initialMode = AnalyzeFile;
1596     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597       initialMode = AnalyzeMode;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599       initialMode = MachinePlaysWhite;
1600     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601       initialMode = MachinePlaysBlack;
1602     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603       initialMode = EditGame;
1604     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605       initialMode = EditPosition;
1606     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607       initialMode = Training;
1608     } else {
1609       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610       if( (len >= MSG_SIZ) && appData.debugMode )
1611         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1612
1613       DisplayFatalError(buf, 0, 2);
1614       return;
1615     }
1616
1617     if (appData.matchMode) {
1618         if(appData.tourneyFile[0]) { // start tourney from command line
1619             FILE *f;
1620             if(f = fopen(appData.tourneyFile, "r")) {
1621                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1622                 fclose(f);
1623                 appData.clockMode = TRUE;
1624                 SetGNUMode();
1625             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1626         }
1627         MatchEvent(TRUE);
1628     } else if (*appData.cmailGameName != NULLCHAR) {
1629         /* Set up cmail mode */
1630         ReloadCmailMsgEvent(TRUE);
1631     } else {
1632         /* Set up other modes */
1633         if (initialMode == AnalyzeFile) {
1634           if (*appData.loadGameFile == NULLCHAR) {
1635             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1636             return;
1637           }
1638         }
1639         if (*appData.loadGameFile != NULLCHAR) {
1640             (void) LoadGameFromFile(appData.loadGameFile,
1641                                     appData.loadGameIndex,
1642                                     appData.loadGameFile, TRUE);
1643         } else if (*appData.loadPositionFile != NULLCHAR) {
1644             (void) LoadPositionFromFile(appData.loadPositionFile,
1645                                         appData.loadPositionIndex,
1646                                         appData.loadPositionFile);
1647             /* [HGM] try to make self-starting even after FEN load */
1648             /* to allow automatic setup of fairy variants with wtm */
1649             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650                 gameMode = BeginningOfGame;
1651                 setboardSpoiledMachineBlack = 1;
1652             }
1653             /* [HGM] loadPos: make that every new game uses the setup */
1654             /* from file as long as we do not switch variant          */
1655             if(!blackPlaysFirst) {
1656                 startedFromPositionFile = TRUE;
1657                 CopyBoard(filePosition, boards[0]);
1658             }
1659         }
1660         if (initialMode == AnalyzeMode) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1667             return;
1668           }
1669           AnalyzeModeEvent();
1670         } else if (initialMode == AnalyzeFile) {
1671           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672           ShowThinkingEvent();
1673           AnalyzeFileEvent();
1674           AnalysisPeriodicEvent(1);
1675         } else if (initialMode == MachinePlaysWhite) {
1676           if (appData.noChessProgram) {
1677             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1678                               0, 2);
1679             return;
1680           }
1681           if (appData.icsActive) {
1682             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1683                               0, 2);
1684             return;
1685           }
1686           MachineWhiteEvent();
1687         } else if (initialMode == MachinePlaysBlack) {
1688           if (appData.noChessProgram) {
1689             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1690                               0, 2);
1691             return;
1692           }
1693           if (appData.icsActive) {
1694             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1695                               0, 2);
1696             return;
1697           }
1698           MachineBlackEvent();
1699         } else if (initialMode == TwoMachinesPlay) {
1700           if (appData.noChessProgram) {
1701             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1702                               0, 2);
1703             return;
1704           }
1705           if (appData.icsActive) {
1706             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1707                               0, 2);
1708             return;
1709           }
1710           TwoMachinesEvent();
1711         } else if (initialMode == EditGame) {
1712           EditGameEvent();
1713         } else if (initialMode == EditPosition) {
1714           EditPositionEvent();
1715         } else if (initialMode == Training) {
1716           if (*appData.loadGameFile == NULLCHAR) {
1717             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1718             return;
1719           }
1720           TrainingEvent();
1721         }
1722     }
1723 }
1724
1725 void
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1727 {
1728     DisplayBook(current+1);
1729
1730     MoveHistorySet( movelist, first, last, current, pvInfoList );
1731
1732     EvalGraphSet( first, last, current, pvInfoList );
1733
1734     MakeEngineOutputTitle();
1735 }
1736
1737 /*
1738  * Establish will establish a contact to a remote host.port.
1739  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740  *  used to talk to the host.
1741  * Returns 0 if okay, error code if not.
1742  */
1743 int
1744 establish ()
1745 {
1746     char buf[MSG_SIZ];
1747
1748     if (*appData.icsCommPort != NULLCHAR) {
1749         /* Talk to the host through a serial comm port */
1750         return OpenCommPort(appData.icsCommPort, &icsPR);
1751
1752     } else if (*appData.gateway != NULLCHAR) {
1753         if (*appData.remoteShell == NULLCHAR) {
1754             /* Use the rcmd protocol to run telnet program on a gateway host */
1755             snprintf(buf, sizeof(buf), "%s %s %s",
1756                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1757             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1758
1759         } else {
1760             /* Use the rsh program to run telnet program on a gateway host */
1761             if (*appData.remoteUser == NULLCHAR) {
1762                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763                         appData.gateway, appData.telnetProgram,
1764                         appData.icsHost, appData.icsPort);
1765             } else {
1766                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767                         appData.remoteShell, appData.gateway,
1768                         appData.remoteUser, appData.telnetProgram,
1769                         appData.icsHost, appData.icsPort);
1770             }
1771             return StartChildProcess(buf, "", &icsPR);
1772
1773         }
1774     } else if (appData.useTelnet) {
1775         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1776
1777     } else {
1778         /* TCP socket interface differs somewhat between
1779            Unix and NT; handle details in the front end.
1780            */
1781         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1782     }
1783 }
1784
1785 void
1786 EscapeExpand (char *p, char *q)
1787 {       // [HGM] initstring: routine to shape up string arguments
1788         while(*p++ = *q++) if(p[-1] == '\\')
1789             switch(*q++) {
1790                 case 'n': p[-1] = '\n'; break;
1791                 case 'r': p[-1] = '\r'; break;
1792                 case 't': p[-1] = '\t'; break;
1793                 case '\\': p[-1] = '\\'; break;
1794                 case 0: *p = 0; return;
1795                 default: p[-1] = q[-1]; break;
1796             }
1797 }
1798
1799 void
1800 show_bytes (FILE *fp, char *buf, int count)
1801 {
1802     while (count--) {
1803         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804             fprintf(fp, "\\%03o", *buf & 0xff);
1805         } else {
1806             putc(*buf, fp);
1807         }
1808         buf++;
1809     }
1810     fflush(fp);
1811 }
1812
1813 /* Returns an errno value */
1814 int
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1816 {
1817     char buf[8192], *p, *q, *buflim;
1818     int left, newcount, outcount;
1819
1820     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821         *appData.gateway != NULLCHAR) {
1822         if (appData.debugMode) {
1823             fprintf(debugFP, ">ICS: ");
1824             show_bytes(debugFP, message, count);
1825             fprintf(debugFP, "\n");
1826         }
1827         return OutputToProcess(pr, message, count, outError);
1828     }
1829
1830     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1831     p = message;
1832     q = buf;
1833     left = count;
1834     newcount = 0;
1835     while (left) {
1836         if (q >= buflim) {
1837             if (appData.debugMode) {
1838                 fprintf(debugFP, ">ICS: ");
1839                 show_bytes(debugFP, buf, newcount);
1840                 fprintf(debugFP, "\n");
1841             }
1842             outcount = OutputToProcess(pr, buf, newcount, outError);
1843             if (outcount < newcount) return -1; /* to be sure */
1844             q = buf;
1845             newcount = 0;
1846         }
1847         if (*p == '\n') {
1848             *q++ = '\r';
1849             newcount++;
1850         } else if (((unsigned char) *p) == TN_IAC) {
1851             *q++ = (char) TN_IAC;
1852             newcount ++;
1853         }
1854         *q++ = *p++;
1855         newcount++;
1856         left--;
1857     }
1858     if (appData.debugMode) {
1859         fprintf(debugFP, ">ICS: ");
1860         show_bytes(debugFP, buf, newcount);
1861         fprintf(debugFP, "\n");
1862     }
1863     outcount = OutputToProcess(pr, buf, newcount, outError);
1864     if (outcount < newcount) return -1; /* to be sure */
1865     return count;
1866 }
1867
1868 void
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1870 {
1871     int outError, outCount;
1872     static int gotEof = 0;
1873     static FILE *ini;
1874
1875     /* Pass data read from player on to ICS */
1876     if (count > 0) {
1877         gotEof = 0;
1878         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879         if (outCount < count) {
1880             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881         }
1882         if(have_sent_ICS_logon == 2) {
1883           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884             fprintf(ini, "%s", message);
1885             have_sent_ICS_logon = 3;
1886           } else
1887             have_sent_ICS_logon = 1;
1888         } else if(have_sent_ICS_logon == 3) {
1889             fprintf(ini, "%s", message);
1890             fclose(ini);
1891           have_sent_ICS_logon = 1;
1892         }
1893     } else if (count < 0) {
1894         RemoveInputSource(isr);
1895         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896     } else if (gotEof++ > 0) {
1897         RemoveInputSource(isr);
1898         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1899     }
1900 }
1901
1902 void
1903 KeepAlive ()
1904 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907     SendToICS("date\n");
1908     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1909 }
1910
1911 /* added routine for printf style output to ics */
1912 void
1913 ics_printf (char *format, ...)
1914 {
1915     char buffer[MSG_SIZ];
1916     va_list args;
1917
1918     va_start(args, format);
1919     vsnprintf(buffer, sizeof(buffer), format, args);
1920     buffer[sizeof(buffer)-1] = '\0';
1921     SendToICS(buffer);
1922     va_end(args);
1923 }
1924
1925 void
1926 SendToICS (char *s)
1927 {
1928     int count, outCount, outError;
1929
1930     if (icsPR == NoProc) return;
1931
1932     count = strlen(s);
1933     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934     if (outCount < count) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 /* This is used for sending logon scripts to the ICS. Sending
1940    without a delay causes problems when using timestamp on ICC
1941    (at least on my machine). */
1942 void
1943 SendToICSDelayed (char *s, long msdelay)
1944 {
1945     int count, outCount, outError;
1946
1947     if (icsPR == NoProc) return;
1948
1949     count = strlen(s);
1950     if (appData.debugMode) {
1951         fprintf(debugFP, ">ICS: ");
1952         show_bytes(debugFP, s, count);
1953         fprintf(debugFP, "\n");
1954     }
1955     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1956                                       msdelay);
1957     if (outCount < count) {
1958         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1959     }
1960 }
1961
1962
1963 /* Remove all highlighting escape sequences in s
1964    Also deletes any suffix starting with '('
1965    */
1966 char *
1967 StripHighlightAndTitle (char *s)
1968 {
1969     static char retbuf[MSG_SIZ];
1970     char *p = retbuf;
1971
1972     while (*s != NULLCHAR) {
1973         while (*s == '\033') {
1974             while (*s != NULLCHAR && !isalpha(*s)) s++;
1975             if (*s != NULLCHAR) s++;
1976         }
1977         while (*s != NULLCHAR && *s != '\033') {
1978             if (*s == '(' || *s == '[') {
1979                 *p = NULLCHAR;
1980                 return retbuf;
1981             }
1982             *p++ = *s++;
1983         }
1984     }
1985     *p = NULLCHAR;
1986     return retbuf;
1987 }
1988
1989 /* Remove all highlighting escape sequences in s */
1990 char *
1991 StripHighlight (char *s)
1992 {
1993     static char retbuf[MSG_SIZ];
1994     char *p = retbuf;
1995
1996     while (*s != NULLCHAR) {
1997         while (*s == '\033') {
1998             while (*s != NULLCHAR && !isalpha(*s)) s++;
1999             if (*s != NULLCHAR) s++;
2000         }
2001         while (*s != NULLCHAR && *s != '\033') {
2002             *p++ = *s++;
2003         }
2004     }
2005     *p = NULLCHAR;
2006     return retbuf;
2007 }
2008
2009 char engineVariant[MSG_SIZ];
2010 char *variantNames[] = VARIANT_NAMES;
2011 char *
2012 VariantName (VariantClass v)
2013 {
2014     if(v == VariantUnknown || *engineVariant) return engineVariant;
2015     return variantNames[v];
2016 }
2017
2018
2019 /* Identify a variant from the strings the chess servers use or the
2020    PGN Variant tag names we use. */
2021 VariantClass
2022 StringToVariant (char *e)
2023 {
2024     char *p;
2025     int wnum = -1;
2026     VariantClass v = VariantNormal;
2027     int i, found = FALSE;
2028     char buf[MSG_SIZ];
2029     int len;
2030
2031     if (!e) return v;
2032
2033     /* [HGM] skip over optional board-size prefixes */
2034     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2035         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2036         while( *e++ != '_');
2037     }
2038
2039     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2040         v = VariantNormal;
2041         found = TRUE;
2042     } else
2043     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2044       if (StrCaseStr(e, variantNames[i])) {
2045         v = (VariantClass) i;
2046         found = TRUE;
2047         break;
2048       }
2049     }
2050
2051     if (!found) {
2052       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2053           || StrCaseStr(e, "wild/fr")
2054           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2055         v = VariantFischeRandom;
2056       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2057                  (i = 1, p = StrCaseStr(e, "w"))) {
2058         p += i;
2059         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2060         if (isdigit(*p)) {
2061           wnum = atoi(p);
2062         } else {
2063           wnum = -1;
2064         }
2065         switch (wnum) {
2066         case 0: /* FICS only, actually */
2067         case 1:
2068           /* Castling legal even if K starts on d-file */
2069           v = VariantWildCastle;
2070           break;
2071         case 2:
2072         case 3:
2073         case 4:
2074           /* Castling illegal even if K & R happen to start in
2075              normal positions. */
2076           v = VariantNoCastle;
2077           break;
2078         case 5:
2079         case 7:
2080         case 8:
2081         case 10:
2082         case 11:
2083         case 12:
2084         case 13:
2085         case 14:
2086         case 15:
2087         case 18:
2088         case 19:
2089           /* Castling legal iff K & R start in normal positions */
2090           v = VariantNormal;
2091           break;
2092         case 6:
2093         case 20:
2094         case 21:
2095           /* Special wilds for position setup; unclear what to do here */
2096           v = VariantLoadable;
2097           break;
2098         case 9:
2099           /* Bizarre ICC game */
2100           v = VariantTwoKings;
2101           break;
2102         case 16:
2103           v = VariantKriegspiel;
2104           break;
2105         case 17:
2106           v = VariantLosers;
2107           break;
2108         case 22:
2109           v = VariantFischeRandom;
2110           break;
2111         case 23:
2112           v = VariantCrazyhouse;
2113           break;
2114         case 24:
2115           v = VariantBughouse;
2116           break;
2117         case 25:
2118           v = Variant3Check;
2119           break;
2120         case 26:
2121           /* Not quite the same as FICS suicide! */
2122           v = VariantGiveaway;
2123           break;
2124         case 27:
2125           v = VariantAtomic;
2126           break;
2127         case 28:
2128           v = VariantShatranj;
2129           break;
2130
2131         /* Temporary names for future ICC types.  The name *will* change in
2132            the next xboard/WinBoard release after ICC defines it. */
2133         case 29:
2134           v = Variant29;
2135           break;
2136         case 30:
2137           v = Variant30;
2138           break;
2139         case 31:
2140           v = Variant31;
2141           break;
2142         case 32:
2143           v = Variant32;
2144           break;
2145         case 33:
2146           v = Variant33;
2147           break;
2148         case 34:
2149           v = Variant34;
2150           break;
2151         case 35:
2152           v = Variant35;
2153           break;
2154         case 36:
2155           v = Variant36;
2156           break;
2157         case 37:
2158           v = VariantShogi;
2159           break;
2160         case 38:
2161           v = VariantXiangqi;
2162           break;
2163         case 39:
2164           v = VariantCourier;
2165           break;
2166         case 40:
2167           v = VariantGothic;
2168           break;
2169         case 41:
2170           v = VariantCapablanca;
2171           break;
2172         case 42:
2173           v = VariantKnightmate;
2174           break;
2175         case 43:
2176           v = VariantFairy;
2177           break;
2178         case 44:
2179           v = VariantCylinder;
2180           break;
2181         case 45:
2182           v = VariantFalcon;
2183           break;
2184         case 46:
2185           v = VariantCapaRandom;
2186           break;
2187         case 47:
2188           v = VariantBerolina;
2189           break;
2190         case 48:
2191           v = VariantJanus;
2192           break;
2193         case 49:
2194           v = VariantSuper;
2195           break;
2196         case 50:
2197           v = VariantGreat;
2198           break;
2199         case -1:
2200           /* Found "wild" or "w" in the string but no number;
2201              must assume it's normal chess. */
2202           v = VariantNormal;
2203           break;
2204         default:
2205           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2206           if( (len >= MSG_SIZ) && appData.debugMode )
2207             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2208
2209           DisplayError(buf, 0);
2210           v = VariantUnknown;
2211           break;
2212         }
2213       }
2214     }
2215     if (appData.debugMode) {
2216       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2217               e, wnum, VariantName(v));
2218     }
2219     return v;
2220 }
2221
2222 static int leftover_start = 0, leftover_len = 0;
2223 char star_match[STAR_MATCH_N][MSG_SIZ];
2224
2225 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2226    advance *index beyond it, and set leftover_start to the new value of
2227    *index; else return FALSE.  If pattern contains the character '*', it
2228    matches any sequence of characters not containing '\r', '\n', or the
2229    character following the '*' (if any), and the matched sequence(s) are
2230    copied into star_match.
2231    */
2232 int
2233 looking_at ( char *buf, int *index, char *pattern)
2234 {
2235     char *bufp = &buf[*index], *patternp = pattern;
2236     int star_count = 0;
2237     char *matchp = star_match[0];
2238
2239     for (;;) {
2240         if (*patternp == NULLCHAR) {
2241             *index = leftover_start = bufp - buf;
2242             *matchp = NULLCHAR;
2243             return TRUE;
2244         }
2245         if (*bufp == NULLCHAR) return FALSE;
2246         if (*patternp == '*') {
2247             if (*bufp == *(patternp + 1)) {
2248                 *matchp = NULLCHAR;
2249                 matchp = star_match[++star_count];
2250                 patternp += 2;
2251                 bufp++;
2252                 continue;
2253             } else if (*bufp == '\n' || *bufp == '\r') {
2254                 patternp++;
2255                 if (*patternp == NULLCHAR)
2256                   continue;
2257                 else
2258                   return FALSE;
2259             } else {
2260                 *matchp++ = *bufp++;
2261                 continue;
2262             }
2263         }
2264         if (*patternp != *bufp) return FALSE;
2265         patternp++;
2266         bufp++;
2267     }
2268 }
2269
2270 void
2271 SendToPlayer (char *data, int length)
2272 {
2273     int error, outCount;
2274     outCount = OutputToProcess(NoProc, data, length, &error);
2275     if (outCount < length) {
2276         DisplayFatalError(_("Error writing to display"), error, 1);
2277     }
2278 }
2279
2280 void
2281 PackHolding (char packed[], char *holding)
2282 {
2283     char *p = holding;
2284     char *q = packed;
2285     int runlength = 0;
2286     int curr = 9999;
2287     do {
2288         if (*p == curr) {
2289             runlength++;
2290         } else {
2291             switch (runlength) {
2292               case 0:
2293                 break;
2294               case 1:
2295                 *q++ = curr;
2296                 break;
2297               case 2:
2298                 *q++ = curr;
2299                 *q++ = curr;
2300                 break;
2301               default:
2302                 sprintf(q, "%d", runlength);
2303                 while (*q) q++;
2304                 *q++ = curr;
2305                 break;
2306             }
2307             runlength = 1;
2308             curr = *p;
2309         }
2310     } while (*p++);
2311     *q = NULLCHAR;
2312 }
2313
2314 /* Telnet protocol requests from the front end */
2315 void
2316 TelnetRequest (unsigned char ddww, unsigned char option)
2317 {
2318     unsigned char msg[3];
2319     int outCount, outError;
2320
2321     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2322
2323     if (appData.debugMode) {
2324         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2325         switch (ddww) {
2326           case TN_DO:
2327             ddwwStr = "DO";
2328             break;
2329           case TN_DONT:
2330             ddwwStr = "DONT";
2331             break;
2332           case TN_WILL:
2333             ddwwStr = "WILL";
2334             break;
2335           case TN_WONT:
2336             ddwwStr = "WONT";
2337             break;
2338           default:
2339             ddwwStr = buf1;
2340             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2341             break;
2342         }
2343         switch (option) {
2344           case TN_ECHO:
2345             optionStr = "ECHO";
2346             break;
2347           default:
2348             optionStr = buf2;
2349             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2350             break;
2351         }
2352         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2353     }
2354     msg[0] = TN_IAC;
2355     msg[1] = ddww;
2356     msg[2] = option;
2357     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2358     if (outCount < 3) {
2359         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2360     }
2361 }
2362
2363 void
2364 DoEcho ()
2365 {
2366     if (!appData.icsActive) return;
2367     TelnetRequest(TN_DO, TN_ECHO);
2368 }
2369
2370 void
2371 DontEcho ()
2372 {
2373     if (!appData.icsActive) return;
2374     TelnetRequest(TN_DONT, TN_ECHO);
2375 }
2376
2377 void
2378 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2379 {
2380     /* put the holdings sent to us by the server on the board holdings area */
2381     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2382     char p;
2383     ChessSquare piece;
2384
2385     if(gameInfo.holdingsWidth < 2)  return;
2386     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2387         return; // prevent overwriting by pre-board holdings
2388
2389     if( (int)lowestPiece >= BlackPawn ) {
2390         holdingsColumn = 0;
2391         countsColumn = 1;
2392         holdingsStartRow = BOARD_HEIGHT-1;
2393         direction = -1;
2394     } else {
2395         holdingsColumn = BOARD_WIDTH-1;
2396         countsColumn = BOARD_WIDTH-2;
2397         holdingsStartRow = 0;
2398         direction = 1;
2399     }
2400
2401     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2402         board[i][holdingsColumn] = EmptySquare;
2403         board[i][countsColumn]   = (ChessSquare) 0;
2404     }
2405     while( (p=*holdings++) != NULLCHAR ) {
2406         piece = CharToPiece( ToUpper(p) );
2407         if(piece == EmptySquare) continue;
2408         /*j = (int) piece - (int) WhitePawn;*/
2409         j = PieceToNumber(piece);
2410         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2411         if(j < 0) continue;               /* should not happen */
2412         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2413         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2414         board[holdingsStartRow+j*direction][countsColumn]++;
2415     }
2416 }
2417
2418
2419 void
2420 VariantSwitch (Board board, VariantClass newVariant)
2421 {
2422    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2423    static Board oldBoard;
2424
2425    startedFromPositionFile = FALSE;
2426    if(gameInfo.variant == newVariant) return;
2427
2428    /* [HGM] This routine is called each time an assignment is made to
2429     * gameInfo.variant during a game, to make sure the board sizes
2430     * are set to match the new variant. If that means adding or deleting
2431     * holdings, we shift the playing board accordingly
2432     * This kludge is needed because in ICS observe mode, we get boards
2433     * of an ongoing game without knowing the variant, and learn about the
2434     * latter only later. This can be because of the move list we requested,
2435     * in which case the game history is refilled from the beginning anyway,
2436     * but also when receiving holdings of a crazyhouse game. In the latter
2437     * case we want to add those holdings to the already received position.
2438     */
2439
2440
2441    if (appData.debugMode) {
2442      fprintf(debugFP, "Switch board from %s to %s\n",
2443              VariantName(gameInfo.variant), VariantName(newVariant));
2444      setbuf(debugFP, NULL);
2445    }
2446    shuffleOpenings = 0;       /* [HGM] shuffle */
2447    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2448    switch(newVariant)
2449      {
2450      case VariantShogi:
2451        newWidth = 9;  newHeight = 9;
2452        gameInfo.holdingsSize = 7;
2453      case VariantBughouse:
2454      case VariantCrazyhouse:
2455        newHoldingsWidth = 2; break;
2456      case VariantGreat:
2457        newWidth = 10;
2458      case VariantSuper:
2459        newHoldingsWidth = 2;
2460        gameInfo.holdingsSize = 8;
2461        break;
2462      case VariantGothic:
2463      case VariantCapablanca:
2464      case VariantCapaRandom:
2465        newWidth = 10;
2466      default:
2467        newHoldingsWidth = gameInfo.holdingsSize = 0;
2468      };
2469
2470    if(newWidth  != gameInfo.boardWidth  ||
2471       newHeight != gameInfo.boardHeight ||
2472       newHoldingsWidth != gameInfo.holdingsWidth ) {
2473
2474      /* shift position to new playing area, if needed */
2475      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2476        for(i=0; i<BOARD_HEIGHT; i++)
2477          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2478            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2479              board[i][j];
2480        for(i=0; i<newHeight; i++) {
2481          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2482          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2483        }
2484      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2485        for(i=0; i<BOARD_HEIGHT; i++)
2486          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2487            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2488              board[i][j];
2489      }
2490      board[HOLDINGS_SET] = 0;
2491      gameInfo.boardWidth  = newWidth;
2492      gameInfo.boardHeight = newHeight;
2493      gameInfo.holdingsWidth = newHoldingsWidth;
2494      gameInfo.variant = newVariant;
2495      InitDrawingSizes(-2, 0);
2496    } else gameInfo.variant = newVariant;
2497    CopyBoard(oldBoard, board);   // remember correctly formatted board
2498      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2499    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2500 }
2501
2502 static int loggedOn = FALSE;
2503
2504 /*-- Game start info cache: --*/
2505 int gs_gamenum;
2506 char gs_kind[MSG_SIZ];
2507 static char player1Name[128] = "";
2508 static char player2Name[128] = "";
2509 static char cont_seq[] = "\n\\   ";
2510 static int player1Rating = -1;
2511 static int player2Rating = -1;
2512 /*----------------------------*/
2513
2514 ColorClass curColor = ColorNormal;
2515 int suppressKibitz = 0;
2516
2517 // [HGM] seekgraph
2518 Boolean soughtPending = FALSE;
2519 Boolean seekGraphUp;
2520 #define MAX_SEEK_ADS 200
2521 #define SQUARE 0x80
2522 char *seekAdList[MAX_SEEK_ADS];
2523 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2524 float tcList[MAX_SEEK_ADS];
2525 char colorList[MAX_SEEK_ADS];
2526 int nrOfSeekAds = 0;
2527 int minRating = 1010, maxRating = 2800;
2528 int hMargin = 10, vMargin = 20, h, w;
2529 extern int squareSize, lineGap;
2530
2531 void
2532 PlotSeekAd (int i)
2533 {
2534         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2535         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2536         if(r < minRating+100 && r >=0 ) r = minRating+100;
2537         if(r > maxRating) r = maxRating;
2538         if(tc < 1.f) tc = 1.f;
2539         if(tc > 95.f) tc = 95.f;
2540         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2541         y = ((double)r - minRating)/(maxRating - minRating)
2542             * (h-vMargin-squareSize/8-1) + vMargin;
2543         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2544         if(strstr(seekAdList[i], " u ")) color = 1;
2545         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2546            !strstr(seekAdList[i], "bullet") &&
2547            !strstr(seekAdList[i], "blitz") &&
2548            !strstr(seekAdList[i], "standard") ) color = 2;
2549         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2550         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2551 }
2552
2553 void
2554 PlotSingleSeekAd (int i)
2555 {
2556         PlotSeekAd(i);
2557 }
2558
2559 void
2560 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2561 {
2562         char buf[MSG_SIZ], *ext = "";
2563         VariantClass v = StringToVariant(type);
2564         if(strstr(type, "wild")) {
2565             ext = type + 4; // append wild number
2566             if(v == VariantFischeRandom) type = "chess960"; else
2567             if(v == VariantLoadable) type = "setup"; else
2568             type = VariantName(v);
2569         }
2570         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2571         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2572             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2573             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2574             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2575             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2576             seekNrList[nrOfSeekAds] = nr;
2577             zList[nrOfSeekAds] = 0;
2578             seekAdList[nrOfSeekAds++] = StrSave(buf);
2579             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2580         }
2581 }
2582
2583 void
2584 EraseSeekDot (int i)
2585 {
2586     int x = xList[i], y = yList[i], d=squareSize/4, k;
2587     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2588     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2589     // now replot every dot that overlapped
2590     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2591         int xx = xList[k], yy = yList[k];
2592         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2593             DrawSeekDot(xx, yy, colorList[k]);
2594     }
2595 }
2596
2597 void
2598 RemoveSeekAd (int nr)
2599 {
2600         int i;
2601         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2602             EraseSeekDot(i);
2603             if(seekAdList[i]) free(seekAdList[i]);
2604             seekAdList[i] = seekAdList[--nrOfSeekAds];
2605             seekNrList[i] = seekNrList[nrOfSeekAds];
2606             ratingList[i] = ratingList[nrOfSeekAds];
2607             colorList[i]  = colorList[nrOfSeekAds];
2608             tcList[i] = tcList[nrOfSeekAds];
2609             xList[i]  = xList[nrOfSeekAds];
2610             yList[i]  = yList[nrOfSeekAds];
2611             zList[i]  = zList[nrOfSeekAds];
2612             seekAdList[nrOfSeekAds] = NULL;
2613             break;
2614         }
2615 }
2616
2617 Boolean
2618 MatchSoughtLine (char *line)
2619 {
2620     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2621     int nr, base, inc, u=0; char dummy;
2622
2623     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2625        (u=1) &&
2626        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2627         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2628         // match: compact and save the line
2629         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2630         return TRUE;
2631     }
2632     return FALSE;
2633 }
2634
2635 int
2636 DrawSeekGraph ()
2637 {
2638     int i;
2639     if(!seekGraphUp) return FALSE;
2640     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2641     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2642
2643     DrawSeekBackground(0, 0, w, h);
2644     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2645     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2646     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2647         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2648         yy = h-1-yy;
2649         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2650         if(i%500 == 0) {
2651             char buf[MSG_SIZ];
2652             snprintf(buf, MSG_SIZ, "%d", i);
2653             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2654         }
2655     }
2656     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2657     for(i=1; i<100; i+=(i<10?1:5)) {
2658         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2659         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2660         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2661             char buf[MSG_SIZ];
2662             snprintf(buf, MSG_SIZ, "%d", i);
2663             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2664         }
2665     }
2666     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2667     return TRUE;
2668 }
2669
2670 int
2671 SeekGraphClick (ClickType click, int x, int y, int moving)
2672 {
2673     static int lastDown = 0, displayed = 0, lastSecond;
2674     if(y < 0) return FALSE;
2675     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2676         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2677         if(!seekGraphUp) return FALSE;
2678         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2679         DrawPosition(TRUE, NULL);
2680         return TRUE;
2681     }
2682     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2683         if(click == Release || moving) return FALSE;
2684         nrOfSeekAds = 0;
2685         soughtPending = TRUE;
2686         SendToICS(ics_prefix);
2687         SendToICS("sought\n"); // should this be "sought all"?
2688     } else { // issue challenge based on clicked ad
2689         int dist = 10000; int i, closest = 0, second = 0;
2690         for(i=0; i<nrOfSeekAds; i++) {
2691             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2692             if(d < dist) { dist = d; closest = i; }
2693             second += (d - zList[i] < 120); // count in-range ads
2694             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2695         }
2696         if(dist < 120) {
2697             char buf[MSG_SIZ];
2698             second = (second > 1);
2699             if(displayed != closest || second != lastSecond) {
2700                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2701                 lastSecond = second; displayed = closest;
2702             }
2703             if(click == Press) {
2704                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2705                 lastDown = closest;
2706                 return TRUE;
2707             } // on press 'hit', only show info
2708             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2709             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2710             SendToICS(ics_prefix);
2711             SendToICS(buf);
2712             return TRUE; // let incoming board of started game pop down the graph
2713         } else if(click == Release) { // release 'miss' is ignored
2714             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2715             if(moving == 2) { // right up-click
2716                 nrOfSeekAds = 0; // refresh graph
2717                 soughtPending = TRUE;
2718                 SendToICS(ics_prefix);
2719                 SendToICS("sought\n"); // should this be "sought all"?
2720             }
2721             return TRUE;
2722         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2723         // press miss or release hit 'pop down' seek graph
2724         seekGraphUp = FALSE;
2725         DrawPosition(TRUE, NULL);
2726     }
2727     return TRUE;
2728 }
2729
2730 void
2731 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2732 {
2733 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2734 #define STARTED_NONE 0
2735 #define STARTED_MOVES 1
2736 #define STARTED_BOARD 2
2737 #define STARTED_OBSERVE 3
2738 #define STARTED_HOLDINGS 4
2739 #define STARTED_CHATTER 5
2740 #define STARTED_COMMENT 6
2741 #define STARTED_MOVES_NOHIDE 7
2742
2743     static int started = STARTED_NONE;
2744     static char parse[20000];
2745     static int parse_pos = 0;
2746     static char buf[BUF_SIZE + 1];
2747     static int firstTime = TRUE, intfSet = FALSE;
2748     static ColorClass prevColor = ColorNormal;
2749     static int savingComment = FALSE;
2750     static int cmatch = 0; // continuation sequence match
2751     char *bp;
2752     char str[MSG_SIZ];
2753     int i, oldi;
2754     int buf_len;
2755     int next_out;
2756     int tkind;
2757     int backup;    /* [DM] For zippy color lines */
2758     char *p;
2759     char talker[MSG_SIZ]; // [HGM] chat
2760     int channel;
2761
2762     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2763
2764     if (appData.debugMode) {
2765       if (!error) {
2766         fprintf(debugFP, "<ICS: ");
2767         show_bytes(debugFP, data, count);
2768         fprintf(debugFP, "\n");
2769       }
2770     }
2771
2772     if (appData.debugMode) { int f = forwardMostMove;
2773         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2774                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2775                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2776     }
2777     if (count > 0) {
2778         /* If last read ended with a partial line that we couldn't parse,
2779            prepend it to the new read and try again. */
2780         if (leftover_len > 0) {
2781             for (i=0; i<leftover_len; i++)
2782               buf[i] = buf[leftover_start + i];
2783         }
2784
2785     /* copy new characters into the buffer */
2786     bp = buf + leftover_len;
2787     buf_len=leftover_len;
2788     for (i=0; i<count; i++)
2789     {
2790         // ignore these
2791         if (data[i] == '\r')
2792             continue;
2793
2794         // join lines split by ICS?
2795         if (!appData.noJoin)
2796         {
2797             /*
2798                 Joining just consists of finding matches against the
2799                 continuation sequence, and discarding that sequence
2800                 if found instead of copying it.  So, until a match
2801                 fails, there's nothing to do since it might be the
2802                 complete sequence, and thus, something we don't want
2803                 copied.
2804             */
2805             if (data[i] == cont_seq[cmatch])
2806             {
2807                 cmatch++;
2808                 if (cmatch == strlen(cont_seq))
2809                 {
2810                     cmatch = 0; // complete match.  just reset the counter
2811
2812                     /*
2813                         it's possible for the ICS to not include the space
2814                         at the end of the last word, making our [correct]
2815                         join operation fuse two separate words.  the server
2816                         does this when the space occurs at the width setting.
2817                     */
2818                     if (!buf_len || buf[buf_len-1] != ' ')
2819                     {
2820                         *bp++ = ' ';
2821                         buf_len++;
2822                     }
2823                 }
2824                 continue;
2825             }
2826             else if (cmatch)
2827             {
2828                 /*
2829                     match failed, so we have to copy what matched before
2830                     falling through and copying this character.  In reality,
2831                     this will only ever be just the newline character, but
2832                     it doesn't hurt to be precise.
2833                 */
2834                 strncpy(bp, cont_seq, cmatch);
2835                 bp += cmatch;
2836                 buf_len += cmatch;
2837                 cmatch = 0;
2838             }
2839         }
2840
2841         // copy this char
2842         *bp++ = data[i];
2843         buf_len++;
2844     }
2845
2846         buf[buf_len] = NULLCHAR;
2847 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2848         next_out = 0;
2849         leftover_start = 0;
2850
2851         i = 0;
2852         while (i < buf_len) {
2853             /* Deal with part of the TELNET option negotiation
2854                protocol.  We refuse to do anything beyond the
2855                defaults, except that we allow the WILL ECHO option,
2856                which ICS uses to turn off password echoing when we are
2857                directly connected to it.  We reject this option
2858                if localLineEditing mode is on (always on in xboard)
2859                and we are talking to port 23, which might be a real
2860                telnet server that will try to keep WILL ECHO on permanently.
2861              */
2862             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2863                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2864                 unsigned char option;
2865                 oldi = i;
2866                 switch ((unsigned char) buf[++i]) {
2867                   case TN_WILL:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<WILL ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       case TN_ECHO:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "ECHO ");
2874                         /* Reply only if this is a change, according
2875                            to the protocol rules. */
2876                         if (remoteEchoOption) break;
2877                         if (appData.localLineEditing &&
2878                             atoi(appData.icsPort) == TN_PORT) {
2879                             TelnetRequest(TN_DONT, TN_ECHO);
2880                         } else {
2881                             EchoOff();
2882                             TelnetRequest(TN_DO, TN_ECHO);
2883                             remoteEchoOption = TRUE;
2884                         }
2885                         break;
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we don't want it. */
2890                         TelnetRequest(TN_DONT, option);
2891                         break;
2892                     }
2893                     break;
2894                   case TN_WONT:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<WONT ");
2897                     switch (option = (unsigned char) buf[++i]) {
2898                       case TN_ECHO:
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "ECHO ");
2901                         /* Reply only if this is a change, according
2902                            to the protocol rules. */
2903                         if (!remoteEchoOption) break;
2904                         EchoOn();
2905                         TelnetRequest(TN_DONT, TN_ECHO);
2906                         remoteEchoOption = FALSE;
2907                         break;
2908                       default:
2909                         if (appData.debugMode)
2910                           fprintf(debugFP, "%d ", (unsigned char) option);
2911                         /* Whatever this is, it must already be turned
2912                            off, because we never agree to turn on
2913                            anything non-default, so according to the
2914                            protocol rules, we don't reply. */
2915                         break;
2916                     }
2917                     break;
2918                   case TN_DO:
2919                     if (appData.debugMode)
2920                       fprintf(debugFP, "\n<DO ");
2921                     switch (option = (unsigned char) buf[++i]) {
2922                       default:
2923                         /* Whatever this is, we refuse to do it. */
2924                         if (appData.debugMode)
2925                           fprintf(debugFP, "%d ", option);
2926                         TelnetRequest(TN_WONT, option);
2927                         break;
2928                     }
2929                     break;
2930                   case TN_DONT:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<DONT ");
2933                     switch (option = (unsigned char) buf[++i]) {
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", option);
2937                         /* Whatever this is, we are already not doing
2938                            it, because we never agree to do anything
2939                            non-default, so according to the protocol
2940                            rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_IAC:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<IAC ");
2947                     /* Doubled IAC; pass it through */
2948                     i--;
2949                     break;
2950                   default:
2951                     if (appData.debugMode)
2952                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2953                     /* Drop all other telnet commands on the floor */
2954                     break;
2955                 }
2956                 if (oldi > next_out)
2957                   SendToPlayer(&buf[next_out], oldi - next_out);
2958                 if (++i > next_out)
2959                   next_out = i;
2960                 continue;
2961             }
2962
2963             /* OK, this at least will *usually* work */
2964             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2965                 loggedOn = TRUE;
2966             }
2967
2968             if (loggedOn && !intfSet) {
2969                 if (ics_type == ICS_ICC) {
2970                   snprintf(str, MSG_SIZ,
2971                           "/set-quietly interface %s\n/set-quietly style 12\n",
2972                           programVersion);
2973                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2974                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2975                 } else if (ics_type == ICS_CHESSNET) {
2976                   snprintf(str, MSG_SIZ, "/style 12\n");
2977                 } else {
2978                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2979                   strcat(str, programVersion);
2980                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2981                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2982                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2983 #ifdef WIN32
2984                   strcat(str, "$iset nohighlight 1\n");
2985 #endif
2986                   strcat(str, "$iset lock 1\n$style 12\n");
2987                 }
2988                 SendToICS(str);
2989                 NotifyFrontendLogin();
2990                 intfSet = TRUE;
2991             }
2992
2993             if (started == STARTED_COMMENT) {
2994                 /* Accumulate characters in comment */
2995                 parse[parse_pos++] = buf[i];
2996                 if (buf[i] == '\n') {
2997                     parse[parse_pos] = NULLCHAR;
2998                     if(chattingPartner>=0) {
2999                         char mess[MSG_SIZ];
3000                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3001                         OutputChatMessage(chattingPartner, mess);
3002                         chattingPartner = -1;
3003                         next_out = i+1; // [HGM] suppress printing in ICS window
3004                     } else
3005                     if(!suppressKibitz) // [HGM] kibitz
3006                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3007                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3008                         int nrDigit = 0, nrAlph = 0, j;
3009                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3010                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3011                         parse[parse_pos] = NULLCHAR;
3012                         // try to be smart: if it does not look like search info, it should go to
3013                         // ICS interaction window after all, not to engine-output window.
3014                         for(j=0; j<parse_pos; j++) { // count letters and digits
3015                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3016                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3017                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3018                         }
3019                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3020                             int depth=0; float score;
3021                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3022                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3023                                 pvInfoList[forwardMostMove-1].depth = depth;
3024                                 pvInfoList[forwardMostMove-1].score = 100*score;
3025                             }
3026                             OutputKibitz(suppressKibitz, parse);
3027                         } else {
3028                             char tmp[MSG_SIZ];
3029                             if(gameMode == IcsObserving) // restore original ICS messages
3030                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3031                             else
3032                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3033                             SendToPlayer(tmp, strlen(tmp));
3034                         }
3035                         next_out = i+1; // [HGM] suppress printing in ICS window
3036                     }
3037                     started = STARTED_NONE;
3038                 } else {
3039                     /* Don't match patterns against characters in comment */
3040                     i++;
3041                     continue;
3042                 }
3043             }
3044             if (started == STARTED_CHATTER) {
3045                 if (buf[i] != '\n') {
3046                     /* Don't match patterns against characters in chatter */
3047                     i++;
3048                     continue;
3049                 }
3050                 started = STARTED_NONE;
3051                 if(suppressKibitz) next_out = i+1;
3052             }
3053
3054             /* Kludge to deal with rcmd protocol */
3055             if (firstTime && looking_at(buf, &i, "\001*")) {
3056                 DisplayFatalError(&buf[1], 0, 1);
3057                 continue;
3058             } else {
3059                 firstTime = FALSE;
3060             }
3061
3062             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3063                 ics_type = ICS_ICC;
3064                 ics_prefix = "/";
3065                 if (appData.debugMode)
3066                   fprintf(debugFP, "ics_type %d\n", ics_type);
3067                 continue;
3068             }
3069             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3070                 ics_type = ICS_FICS;
3071                 ics_prefix = "$";
3072                 if (appData.debugMode)
3073                   fprintf(debugFP, "ics_type %d\n", ics_type);
3074                 continue;
3075             }
3076             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3077                 ics_type = ICS_CHESSNET;
3078                 ics_prefix = "/";
3079                 if (appData.debugMode)
3080                   fprintf(debugFP, "ics_type %d\n", ics_type);
3081                 continue;
3082             }
3083
3084             if (!loggedOn &&
3085                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3086                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3087                  looking_at(buf, &i, "will be \"*\""))) {
3088               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3089               continue;
3090             }
3091
3092             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3093               char buf[MSG_SIZ];
3094               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3095               DisplayIcsInteractionTitle(buf);
3096               have_set_title = TRUE;
3097             }
3098
3099             /* skip finger notes */
3100             if (started == STARTED_NONE &&
3101                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3102                  (buf[i] == '1' && buf[i+1] == '0')) &&
3103                 buf[i+2] == ':' && buf[i+3] == ' ') {
3104               started = STARTED_CHATTER;
3105               i += 3;
3106               continue;
3107             }
3108
3109             oldi = i;
3110             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3111             if(appData.seekGraph) {
3112                 if(soughtPending && MatchSoughtLine(buf+i)) {
3113                     i = strstr(buf+i, "rated") - buf;
3114                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115                     next_out = leftover_start = i;
3116                     started = STARTED_CHATTER;
3117                     suppressKibitz = TRUE;
3118                     continue;
3119                 }
3120                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3121                         && looking_at(buf, &i, "* ads displayed")) {
3122                     soughtPending = FALSE;
3123                     seekGraphUp = TRUE;
3124                     DrawSeekGraph();
3125                     continue;
3126                 }
3127                 if(appData.autoRefresh) {
3128                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3129                         int s = (ics_type == ICS_ICC); // ICC format differs
3130                         if(seekGraphUp)
3131                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3132                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3133                         looking_at(buf, &i, "*% "); // eat prompt
3134                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3135                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136                         next_out = i; // suppress
3137                         continue;
3138                     }
3139                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3140                         char *p = star_match[0];
3141                         while(*p) {
3142                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3143                             while(*p && *p++ != ' '); // next
3144                         }
3145                         looking_at(buf, &i, "*% "); // eat prompt
3146                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3147                         next_out = i;
3148                         continue;
3149                     }
3150                 }
3151             }
3152
3153             /* skip formula vars */
3154             if (started == STARTED_NONE &&
3155                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3156               started = STARTED_CHATTER;
3157               i += 3;
3158               continue;
3159             }
3160
3161             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3162             if (appData.autoKibitz && started == STARTED_NONE &&
3163                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3164                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3165                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3166                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3167                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3168                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3169                         suppressKibitz = TRUE;
3170                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3171                         next_out = i;
3172                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3173                                 && (gameMode == IcsPlayingWhite)) ||
3174                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3175                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3176                             started = STARTED_CHATTER; // own kibitz we simply discard
3177                         else {
3178                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3179                             parse_pos = 0; parse[0] = NULLCHAR;
3180                             savingComment = TRUE;
3181                             suppressKibitz = gameMode != IcsObserving ? 2 :
3182                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3183                         }
3184                         continue;
3185                 } else
3186                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3187                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3188                          && atoi(star_match[0])) {
3189                     // suppress the acknowledgements of our own autoKibitz
3190                     char *p;
3191                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3193                     SendToPlayer(star_match[0], strlen(star_match[0]));
3194                     if(looking_at(buf, &i, "*% ")) // eat prompt
3195                         suppressKibitz = FALSE;
3196                     next_out = i;
3197                     continue;
3198                 }
3199             } // [HGM] kibitz: end of patch
3200
3201             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3202
3203             // [HGM] chat: intercept tells by users for which we have an open chat window
3204             channel = -1;
3205             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3206                                            looking_at(buf, &i, "* whispers:") ||
3207                                            looking_at(buf, &i, "* kibitzes:") ||
3208                                            looking_at(buf, &i, "* shouts:") ||
3209                                            looking_at(buf, &i, "* c-shouts:") ||
3210                                            looking_at(buf, &i, "--> * ") ||
3211                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3212                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3213                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3214                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3215                 int p;
3216                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3217                 chattingPartner = -1;
3218
3219                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3220                 for(p=0; p<MAX_CHAT; p++) {
3221                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3222                     talker[0] = '['; strcat(talker, "] ");
3223                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3224                     chattingPartner = p; break;
3225                     }
3226                 } else
3227                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3228                 for(p=0; p<MAX_CHAT; p++) {
3229                     if(!strcmp("kibitzes", chatPartner[p])) {
3230                         talker[0] = '['; strcat(talker, "] ");
3231                         chattingPartner = p; break;
3232                     }
3233                 } else
3234                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3235                 for(p=0; p<MAX_CHAT; p++) {
3236                     if(!strcmp("whispers", chatPartner[p])) {
3237                         talker[0] = '['; strcat(talker, "] ");
3238                         chattingPartner = p; break;
3239                     }
3240                 } else
3241                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3242                   if(buf[i-8] == '-' && buf[i-3] == 't')
3243                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3244                     if(!strcmp("c-shouts", chatPartner[p])) {
3245                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3246                         chattingPartner = p; break;
3247                     }
3248                   }
3249                   if(chattingPartner < 0)
3250                   for(p=0; p<MAX_CHAT; p++) {
3251                     if(!strcmp("shouts", chatPartner[p])) {
3252                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3253                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3254                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3255                         chattingPartner = p; break;
3256                     }
3257                   }
3258                 }
3259                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3260                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3261                     talker[0] = 0; Colorize(ColorTell, FALSE);
3262                     chattingPartner = p; break;
3263                 }
3264                 if(chattingPartner<0) i = oldi; else {
3265                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3266                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3267                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3268                     started = STARTED_COMMENT;
3269                     parse_pos = 0; parse[0] = NULLCHAR;
3270                     savingComment = 3 + chattingPartner; // counts as TRUE
3271                     suppressKibitz = TRUE;
3272                     continue;
3273                 }
3274             } // [HGM] chat: end of patch
3275
3276           backup = i;
3277             if (appData.zippyTalk || appData.zippyPlay) {
3278                 /* [DM] Backup address for color zippy lines */
3279 #if ZIPPY
3280                if (loggedOn == TRUE)
3281                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3282                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3283 #endif
3284             } // [DM] 'else { ' deleted
3285                 if (
3286                     /* Regular tells and says */
3287                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3288                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3289                     looking_at(buf, &i, "* says: ") ||
3290                     /* Don't color "message" or "messages" output */
3291                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3292                     looking_at(buf, &i, "*. * at *:*: ") ||
3293                     looking_at(buf, &i, "--* (*:*): ") ||
3294                     /* Message notifications (same color as tells) */
3295                     looking_at(buf, &i, "* has left a message ") ||
3296                     looking_at(buf, &i, "* just sent you a message:\n") ||
3297                     /* Whispers and kibitzes */
3298                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3299                     looking_at(buf, &i, "* kibitzes: ") ||
3300                     /* Channel tells */
3301                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3302
3303                   if (tkind == 1 && strchr(star_match[0], ':')) {
3304                       /* Avoid "tells you:" spoofs in channels */
3305                      tkind = 3;
3306                   }
3307                   if (star_match[0][0] == NULLCHAR ||
3308                       strchr(star_match[0], ' ') ||
3309                       (tkind == 3 && strchr(star_match[1], ' '))) {
3310                     /* Reject bogus matches */
3311                     i = oldi;
3312                   } else {
3313                     if (appData.colorize) {
3314                       if (oldi > next_out) {
3315                         SendToPlayer(&buf[next_out], oldi - next_out);
3316                         next_out = oldi;
3317                       }
3318                       switch (tkind) {
3319                       case 1:
3320                         Colorize(ColorTell, FALSE);
3321                         curColor = ColorTell;
3322                         break;
3323                       case 2:
3324                         Colorize(ColorKibitz, FALSE);
3325                         curColor = ColorKibitz;
3326                         break;
3327                       case 3:
3328                         p = strrchr(star_match[1], '(');
3329                         if (p == NULL) {
3330                           p = star_match[1];
3331                         } else {
3332                           p++;
3333                         }
3334                         if (atoi(p) == 1) {
3335                           Colorize(ColorChannel1, FALSE);
3336                           curColor = ColorChannel1;
3337                         } else {
3338                           Colorize(ColorChannel, FALSE);
3339                           curColor = ColorChannel;
3340                         }
3341                         break;
3342                       case 5:
3343                         curColor = ColorNormal;
3344                         break;
3345                       }
3346                     }
3347                     if (started == STARTED_NONE && appData.autoComment &&
3348                         (gameMode == IcsObserving ||
3349                          gameMode == IcsPlayingWhite ||
3350                          gameMode == IcsPlayingBlack)) {
3351                       parse_pos = i - oldi;
3352                       memcpy(parse, &buf[oldi], parse_pos);
3353                       parse[parse_pos] = NULLCHAR;
3354                       started = STARTED_COMMENT;
3355                       savingComment = TRUE;
3356                     } else {
3357                       started = STARTED_CHATTER;
3358                       savingComment = FALSE;
3359                     }
3360                     loggedOn = TRUE;
3361                     continue;
3362                   }
3363                 }
3364
3365                 if (looking_at(buf, &i, "* s-shouts: ") ||
3366                     looking_at(buf, &i, "* c-shouts: ")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSShout, FALSE);
3373                         curColor = ColorSShout;
3374                     }
3375                     loggedOn = TRUE;
3376                     started = STARTED_CHATTER;
3377                     continue;
3378                 }
3379
3380                 if (looking_at(buf, &i, "--->")) {
3381                     loggedOn = TRUE;
3382                     continue;
3383                 }
3384
3385                 if (looking_at(buf, &i, "* shouts: ") ||
3386                     looking_at(buf, &i, "--> ")) {
3387                     if (appData.colorize) {
3388                         if (oldi > next_out) {
3389                             SendToPlayer(&buf[next_out], oldi - next_out);
3390                             next_out = oldi;
3391                         }
3392                         Colorize(ColorShout, FALSE);
3393                         curColor = ColorShout;
3394                     }
3395                     loggedOn = TRUE;
3396                     started = STARTED_CHATTER;
3397                     continue;
3398                 }
3399
3400                 if (looking_at( buf, &i, "Challenge:")) {
3401                     if (appData.colorize) {
3402                         if (oldi > next_out) {
3403                             SendToPlayer(&buf[next_out], oldi - next_out);
3404                             next_out = oldi;
3405                         }
3406                         Colorize(ColorChallenge, FALSE);
3407                         curColor = ColorChallenge;
3408                     }
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* offers you") ||
3414                     looking_at(buf, &i, "* offers to be") ||
3415                     looking_at(buf, &i, "* would like to") ||
3416                     looking_at(buf, &i, "* requests to") ||
3417                     looking_at(buf, &i, "Your opponent offers") ||
3418                     looking_at(buf, &i, "Your opponent requests")) {
3419
3420                     if (appData.colorize) {
3421                         if (oldi > next_out) {
3422                             SendToPlayer(&buf[next_out], oldi - next_out);
3423                             next_out = oldi;
3424                         }
3425                         Colorize(ColorRequest, FALSE);
3426                         curColor = ColorRequest;
3427                     }
3428                     continue;
3429                 }
3430
3431                 if (looking_at(buf, &i, "* (*) seeking")) {
3432                     if (appData.colorize) {
3433                         if (oldi > next_out) {
3434                             SendToPlayer(&buf[next_out], oldi - next_out);
3435                             next_out = oldi;
3436                         }
3437                         Colorize(ColorSeek, FALSE);
3438                         curColor = ColorSeek;
3439                     }
3440                     continue;
3441             }
3442
3443           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3444
3445             if (looking_at(buf, &i, "\\   ")) {
3446                 if (prevColor != ColorNormal) {
3447                     if (oldi > next_out) {
3448                         SendToPlayer(&buf[next_out], oldi - next_out);
3449                         next_out = oldi;
3450                     }
3451                     Colorize(prevColor, TRUE);
3452                     curColor = prevColor;
3453                 }
3454                 if (savingComment) {
3455                     parse_pos = i - oldi;
3456                     memcpy(parse, &buf[oldi], parse_pos);
3457                     parse[parse_pos] = NULLCHAR;
3458                     started = STARTED_COMMENT;
3459                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3460                         chattingPartner = savingComment - 3; // kludge to remember the box
3461                 } else {
3462                     started = STARTED_CHATTER;
3463                 }
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "Black Strength :") ||
3468                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3469                 looking_at(buf, &i, "<10>") ||
3470                 looking_at(buf, &i, "#@#")) {
3471                 /* Wrong board style */
3472                 loggedOn = TRUE;
3473                 SendToICS(ics_prefix);
3474                 SendToICS("set style 12\n");
3475                 SendToICS(ics_prefix);
3476                 SendToICS("refresh\n");
3477                 continue;
3478             }
3479
3480             if (looking_at(buf, &i, "login:")) {
3481               if (!have_sent_ICS_logon) {
3482                 if(ICSInitScript())
3483                   have_sent_ICS_logon = 1;
3484                 else // no init script was found
3485                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3486               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3487                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3488               }
3489                 continue;
3490             }
3491
3492             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3493                 (looking_at(buf, &i, "\n<12> ") ||
3494                  looking_at(buf, &i, "<12> "))) {
3495                 loggedOn = TRUE;
3496                 if (oldi > next_out) {
3497                     SendToPlayer(&buf[next_out], oldi - next_out);
3498                 }
3499                 next_out = i;
3500                 started = STARTED_BOARD;
3501                 parse_pos = 0;
3502                 continue;
3503             }
3504
3505             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3506                 looking_at(buf, &i, "<b1> ")) {
3507                 if (oldi > next_out) {
3508                     SendToPlayer(&buf[next_out], oldi - next_out);
3509                 }
3510                 next_out = i;
3511                 started = STARTED_HOLDINGS;
3512                 parse_pos = 0;
3513                 continue;
3514             }
3515
3516             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3517                 loggedOn = TRUE;
3518                 /* Header for a move list -- first line */
3519
3520                 switch (ics_getting_history) {
3521                   case H_FALSE:
3522                     switch (gameMode) {
3523                       case IcsIdle:
3524                       case BeginningOfGame:
3525                         /* User typed "moves" or "oldmoves" while we
3526                            were idle.  Pretend we asked for these
3527                            moves and soak them up so user can step
3528                            through them and/or save them.
3529                            */
3530                         Reset(FALSE, TRUE);
3531                         gameMode = IcsObserving;
3532                         ModeHighlight();
3533                         ics_gamenum = -1;
3534                         ics_getting_history = H_GOT_UNREQ_HEADER;
3535                         break;
3536                       case EditGame: /*?*/
3537                       case EditPosition: /*?*/
3538                         /* Should above feature work in these modes too? */
3539                         /* For now it doesn't */
3540                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3541                         break;
3542                       default:
3543                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3544                         break;
3545                     }
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Is this the right one? */
3549                     if (gameInfo.white && gameInfo.black &&
3550                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3551                         strcmp(gameInfo.black, star_match[2]) == 0) {
3552                         /* All is well */
3553                         ics_getting_history = H_GOT_REQ_HEADER;
3554                     }
3555                     break;
3556                   case H_GOT_REQ_HEADER:
3557                   case H_GOT_UNREQ_HEADER:
3558                   case H_GOT_UNWANTED_HEADER:
3559                   case H_GETTING_MOVES:
3560                     /* Should not happen */
3561                     DisplayError(_("Error gathering move list: two headers"), 0);
3562                     ics_getting_history = H_FALSE;
3563                     break;
3564                 }
3565
3566                 /* Save player ratings into gameInfo if needed */
3567                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3568                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3569                     (gameInfo.whiteRating == -1 ||
3570                      gameInfo.blackRating == -1)) {
3571
3572                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3573                     gameInfo.blackRating = string_to_rating(star_match[3]);
3574                     if (appData.debugMode)
3575                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3576                               gameInfo.whiteRating, gameInfo.blackRating);
3577                 }
3578                 continue;
3579             }
3580
3581             if (looking_at(buf, &i,
3582               "* * match, initial time: * minute*, increment: * second")) {
3583                 /* Header for a move list -- second line */
3584                 /* Initial board will follow if this is a wild game */
3585                 if (gameInfo.event != NULL) free(gameInfo.event);
3586                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3587                 gameInfo.event = StrSave(str);
3588                 /* [HGM] we switched variant. Translate boards if needed. */
3589                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3590                 continue;
3591             }
3592
3593             if (looking_at(buf, &i, "Move  ")) {
3594                 /* Beginning of a move list */
3595                 switch (ics_getting_history) {
3596                   case H_FALSE:
3597                     /* Normally should not happen */
3598                     /* Maybe user hit reset while we were parsing */
3599                     break;
3600                   case H_REQUESTED:
3601                     /* Happens if we are ignoring a move list that is not
3602                      * the one we just requested.  Common if the user
3603                      * tries to observe two games without turning off
3604                      * getMoveList */
3605                     break;
3606                   case H_GETTING_MOVES:
3607                     /* Should not happen */
3608                     DisplayError(_("Error gathering move list: nested"), 0);
3609                     ics_getting_history = H_FALSE;
3610                     break;
3611                   case H_GOT_REQ_HEADER:
3612                     ics_getting_history = H_GETTING_MOVES;
3613                     started = STARTED_MOVES;
3614                     parse_pos = 0;
3615                     if (oldi > next_out) {
3616                         SendToPlayer(&buf[next_out], oldi - next_out);
3617                     }
3618                     break;
3619                   case H_GOT_UNREQ_HEADER:
3620                     ics_getting_history = H_GETTING_MOVES;
3621                     started = STARTED_MOVES_NOHIDE;
3622                     parse_pos = 0;
3623                     break;
3624                   case H_GOT_UNWANTED_HEADER:
3625                     ics_getting_history = H_FALSE;
3626                     break;
3627                 }
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "% ") ||
3632                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3633                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3634                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3635                     soughtPending = FALSE;
3636                     seekGraphUp = TRUE;
3637                     DrawSeekGraph();
3638                 }
3639                 if(suppressKibitz) next_out = i;
3640                 savingComment = FALSE;
3641                 suppressKibitz = 0;
3642                 switch (started) {
3643                   case STARTED_MOVES:
3644                   case STARTED_MOVES_NOHIDE:
3645                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3646                     parse[parse_pos + i - oldi] = NULLCHAR;
3647                     ParseGameHistory(parse);
3648 #if ZIPPY
3649                     if (appData.zippyPlay && first.initDone) {
3650                         FeedMovesToProgram(&first, forwardMostMove);
3651                         if (gameMode == IcsPlayingWhite) {
3652                             if (WhiteOnMove(forwardMostMove)) {
3653                                 if (first.sendTime) {
3654                                   if (first.useColors) {
3655                                     SendToProgram("black\n", &first);
3656                                   }
3657                                   SendTimeRemaining(&first, TRUE);
3658                                 }
3659                                 if (first.useColors) {
3660                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3661                                 }
3662                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3663                                 first.maybeThinking = TRUE;
3664                             } else {
3665                                 if (first.usePlayother) {
3666                                   if (first.sendTime) {
3667                                     SendTimeRemaining(&first, TRUE);
3668                                   }
3669                                   SendToProgram("playother\n", &first);
3670                                   firstMove = FALSE;
3671                                 } else {
3672                                   firstMove = TRUE;
3673                                 }
3674                             }
3675                         } else if (gameMode == IcsPlayingBlack) {
3676                             if (!WhiteOnMove(forwardMostMove)) {
3677                                 if (first.sendTime) {
3678                                   if (first.useColors) {
3679                                     SendToProgram("white\n", &first);
3680                                   }
3681                                   SendTimeRemaining(&first, FALSE);
3682                                 }
3683                                 if (first.useColors) {
3684                                   SendToProgram("black\n", &first);
3685                                 }
3686                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3687                                 first.maybeThinking = TRUE;
3688                             } else {
3689                                 if (first.usePlayother) {
3690                                   if (first.sendTime) {
3691                                     SendTimeRemaining(&first, FALSE);
3692                                   }
3693                                   SendToProgram("playother\n", &first);
3694                                   firstMove = FALSE;
3695                                 } else {
3696                                   firstMove = TRUE;
3697                                 }
3698                             }
3699                         }
3700                     }
3701 #endif
3702                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3703                         /* Moves came from oldmoves or moves command
3704                            while we weren't doing anything else.
3705                            */
3706                         currentMove = forwardMostMove;
3707                         ClearHighlights();/*!!could figure this out*/
3708                         flipView = appData.flipView;
3709                         DrawPosition(TRUE, boards[currentMove]);
3710                         DisplayBothClocks();
3711                         snprintf(str, MSG_SIZ, "%s %s %s",
3712                                 gameInfo.white, _("vs."),  gameInfo.black);
3713                         DisplayTitle(str);
3714                         gameMode = IcsIdle;
3715                     } else {
3716                         /* Moves were history of an active game */
3717                         if (gameInfo.resultDetails != NULL) {
3718                             free(gameInfo.resultDetails);
3719                             gameInfo.resultDetails = NULL;
3720                         }
3721                     }
3722                     HistorySet(parseList, backwardMostMove,
3723                                forwardMostMove, currentMove-1);
3724                     DisplayMove(currentMove - 1);
3725                     if (started == STARTED_MOVES) next_out = i;
3726                     started = STARTED_NONE;
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729
3730                   case STARTED_OBSERVE:
3731                     started = STARTED_NONE;
3732                     SendToICS(ics_prefix);
3733                     SendToICS("refresh\n");
3734                     break;
3735
3736                   default:
3737                     break;
3738                 }
3739                 if(bookHit) { // [HGM] book: simulate book reply
3740                     static char bookMove[MSG_SIZ]; // a bit generous?
3741
3742                     programStats.nodes = programStats.depth = programStats.time =
3743                     programStats.score = programStats.got_only_move = 0;
3744                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3745
3746                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3747                     strcat(bookMove, bookHit);
3748                     HandleMachineMove(bookMove, &first);
3749                 }
3750                 continue;
3751             }
3752
3753             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3754                  started == STARTED_HOLDINGS ||
3755                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3756                 /* Accumulate characters in move list or board */
3757                 parse[parse_pos++] = buf[i];
3758             }
3759
3760             /* Start of game messages.  Mostly we detect start of game
3761                when the first board image arrives.  On some versions
3762                of the ICS, though, we need to do a "refresh" after starting
3763                to observe in order to get the current board right away. */
3764             if (looking_at(buf, &i, "Adding game * to observation list")) {
3765                 started = STARTED_OBSERVE;
3766                 continue;
3767             }
3768
3769             /* Handle auto-observe */
3770             if (appData.autoObserve &&
3771                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3772                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3773                 char *player;
3774                 /* Choose the player that was highlighted, if any. */
3775                 if (star_match[0][0] == '\033' ||
3776                     star_match[1][0] != '\033') {
3777                     player = star_match[0];
3778                 } else {
3779                     player = star_match[2];
3780                 }
3781                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3782                         ics_prefix, StripHighlightAndTitle(player));
3783                 SendToICS(str);
3784
3785                 /* Save ratings from notify string */
3786                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3787                 player1Rating = string_to_rating(star_match[1]);
3788                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3789                 player2Rating = string_to_rating(star_match[3]);
3790
3791                 if (appData.debugMode)
3792                   fprintf(debugFP,
3793                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3794                           player1Name, player1Rating,
3795                           player2Name, player2Rating);
3796
3797                 continue;
3798             }
3799
3800             /* Deal with automatic examine mode after a game,
3801                and with IcsObserving -> IcsExamining transition */
3802             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3803                 looking_at(buf, &i, "has made you an examiner of game *")) {
3804
3805                 int gamenum = atoi(star_match[0]);
3806                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3807                     gamenum == ics_gamenum) {
3808                     /* We were already playing or observing this game;
3809                        no need to refetch history */
3810                     gameMode = IcsExamining;
3811                     if (pausing) {
3812                         pauseExamForwardMostMove = forwardMostMove;
3813                     } else if (currentMove < forwardMostMove) {
3814                         ForwardInner(forwardMostMove);
3815                     }
3816                 } else {
3817                     /* I don't think this case really can happen */
3818                     SendToICS(ics_prefix);
3819                     SendToICS("refresh\n");
3820                 }
3821                 continue;
3822             }
3823
3824             /* Error messages */
3825 //          if (ics_user_moved) {
3826             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3827                 if (looking_at(buf, &i, "Illegal move") ||
3828                     looking_at(buf, &i, "Not a legal move") ||
3829                     looking_at(buf, &i, "Your king is in check") ||
3830                     looking_at(buf, &i, "It isn't your turn") ||
3831                     looking_at(buf, &i, "It is not your move")) {
3832                     /* Illegal move */
3833                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3834                         currentMove = forwardMostMove-1;
3835                         DisplayMove(currentMove - 1); /* before DMError */
3836                         DrawPosition(FALSE, boards[currentMove]);
3837                         SwitchClocks(forwardMostMove-1); // [HGM] race
3838                         DisplayBothClocks();
3839                     }
3840                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3841                     ics_user_moved = 0;
3842                     continue;
3843                 }
3844             }
3845
3846             if (looking_at(buf, &i, "still have time") ||
3847                 looking_at(buf, &i, "not out of time") ||
3848                 looking_at(buf, &i, "either player is out of time") ||
3849                 looking_at(buf, &i, "has timeseal; checking")) {
3850                 /* We must have called his flag a little too soon */
3851                 whiteFlag = blackFlag = FALSE;
3852                 continue;
3853             }
3854
3855             if (looking_at(buf, &i, "added * seconds to") ||
3856                 looking_at(buf, &i, "seconds were added to")) {
3857                 /* Update the clocks */
3858                 SendToICS(ics_prefix);
3859                 SendToICS("refresh\n");
3860                 continue;
3861             }
3862
3863             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3864                 ics_clock_paused = TRUE;
3865                 StopClocks();
3866                 continue;
3867             }
3868
3869             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3870                 ics_clock_paused = FALSE;
3871                 StartClocks();
3872                 continue;
3873             }
3874
3875             /* Grab player ratings from the Creating: message.
3876                Note we have to check for the special case when
3877                the ICS inserts things like [white] or [black]. */
3878             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3879                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3880                 /* star_matches:
3881                    0    player 1 name (not necessarily white)
3882                    1    player 1 rating
3883                    2    empty, white, or black (IGNORED)
3884                    3    player 2 name (not necessarily black)
3885                    4    player 2 rating
3886
3887                    The names/ratings are sorted out when the game
3888                    actually starts (below).
3889                 */
3890                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3891                 player1Rating = string_to_rating(star_match[1]);
3892                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3893                 player2Rating = string_to_rating(star_match[4]);
3894
3895                 if (appData.debugMode)
3896                   fprintf(debugFP,
3897                           "Ratings from 'Creating:' %s %d, %s %d\n",
3898                           player1Name, player1Rating,
3899                           player2Name, player2Rating);
3900
3901                 continue;
3902             }
3903
3904             /* Improved generic start/end-of-game messages */
3905             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3906                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3907                 /* If tkind == 0: */
3908                 /* star_match[0] is the game number */
3909                 /*           [1] is the white player's name */
3910                 /*           [2] is the black player's name */
3911                 /* For end-of-game: */
3912                 /*           [3] is the reason for the game end */
3913                 /*           [4] is a PGN end game-token, preceded by " " */
3914                 /* For start-of-game: */
3915                 /*           [3] begins with "Creating" or "Continuing" */
3916                 /*           [4] is " *" or empty (don't care). */
3917                 int gamenum = atoi(star_match[0]);
3918                 char *whitename, *blackname, *why, *endtoken;
3919                 ChessMove endtype = EndOfFile;
3920
3921                 if (tkind == 0) {
3922                   whitename = star_match[1];
3923                   blackname = star_match[2];
3924                   why = star_match[3];
3925                   endtoken = star_match[4];
3926                 } else {
3927                   whitename = star_match[1];
3928                   blackname = star_match[3];
3929                   why = star_match[5];
3930                   endtoken = star_match[6];
3931                 }
3932
3933                 /* Game start messages */
3934                 if (strncmp(why, "Creating ", 9) == 0 ||
3935                     strncmp(why, "Continuing ", 11) == 0) {
3936                     gs_gamenum = gamenum;
3937                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3938                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3939                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3940 #if ZIPPY
3941                     if (appData.zippyPlay) {
3942                         ZippyGameStart(whitename, blackname);
3943                     }
3944 #endif /*ZIPPY*/
3945                     partnerBoardValid = FALSE; // [HGM] bughouse
3946                     continue;
3947                 }
3948
3949                 /* Game end messages */
3950                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3951                     ics_gamenum != gamenum) {
3952                     continue;
3953                 }
3954                 while (endtoken[0] == ' ') endtoken++;
3955                 switch (endtoken[0]) {
3956                   case '*':
3957                   default:
3958                     endtype = GameUnfinished;
3959                     break;
3960                   case '0':
3961                     endtype = BlackWins;
3962                     break;
3963                   case '1':
3964                     if (endtoken[1] == '/')
3965                       endtype = GameIsDrawn;
3966                     else
3967                       endtype = WhiteWins;
3968                     break;
3969                 }
3970                 GameEnds(endtype, why, GE_ICS);
3971 #if ZIPPY
3972                 if (appData.zippyPlay && first.initDone) {
3973                     ZippyGameEnd(endtype, why);
3974                     if (first.pr == NoProc) {
3975                       /* Start the next process early so that we'll
3976                          be ready for the next challenge */
3977                       StartChessProgram(&first);
3978                     }
3979                     /* Send "new" early, in case this command takes
3980                        a long time to finish, so that we'll be ready
3981                        for the next challenge. */
3982                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3983                     Reset(TRUE, TRUE);
3984                 }
3985 #endif /*ZIPPY*/
3986                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3987                 continue;
3988             }
3989
3990             if (looking_at(buf, &i, "Removing game * from observation") ||
3991                 looking_at(buf, &i, "no longer observing game *") ||
3992                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3993                 if (gameMode == IcsObserving &&
3994                     atoi(star_match[0]) == ics_gamenum)
3995                   {
3996                       /* icsEngineAnalyze */
3997                       if (appData.icsEngineAnalyze) {
3998                             ExitAnalyzeMode();
3999                             ModeHighlight();
4000                       }
4001                       StopClocks();
4002                       gameMode = IcsIdle;
4003                       ics_gamenum = -1;
4004                       ics_user_moved = FALSE;
4005                   }
4006                 continue;
4007             }
4008
4009             if (looking_at(buf, &i, "no longer examining game *")) {
4010                 if (gameMode == IcsExamining &&
4011                     atoi(star_match[0]) == ics_gamenum)
4012                   {
4013                       gameMode = IcsIdle;
4014                       ics_gamenum = -1;
4015                       ics_user_moved = FALSE;
4016                   }
4017                 continue;
4018             }
4019
4020             /* Advance leftover_start past any newlines we find,
4021                so only partial lines can get reparsed */
4022             if (looking_at(buf, &i, "\n")) {
4023                 prevColor = curColor;
4024                 if (curColor != ColorNormal) {
4025                     if (oldi > next_out) {
4026                         SendToPlayer(&buf[next_out], oldi - next_out);
4027                         next_out = oldi;
4028                     }
4029                     Colorize(ColorNormal, FALSE);
4030                     curColor = ColorNormal;
4031                 }
4032                 if (started == STARTED_BOARD) {
4033                     started = STARTED_NONE;
4034                     parse[parse_pos] = NULLCHAR;
4035                     ParseBoard12(parse);
4036                     ics_user_moved = 0;
4037
4038                     /* Send premove here */
4039                     if (appData.premove) {
4040                       char str[MSG_SIZ];
4041                       if (currentMove == 0 &&
4042                           gameMode == IcsPlayingWhite &&
4043                           appData.premoveWhite) {
4044                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4045                         if (appData.debugMode)
4046                           fprintf(debugFP, "Sending premove:\n");
4047                         SendToICS(str);
4048                       } else if (currentMove == 1 &&
4049                                  gameMode == IcsPlayingBlack &&
4050                                  appData.premoveBlack) {
4051                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4052                         if (appData.debugMode)
4053                           fprintf(debugFP, "Sending premove:\n");
4054                         SendToICS(str);
4055                       } else if (gotPremove) {
4056                         gotPremove = 0;
4057                         ClearPremoveHighlights();
4058                         if (appData.debugMode)
4059                           fprintf(debugFP, "Sending premove:\n");
4060                           UserMoveEvent(premoveFromX, premoveFromY,
4061                                         premoveToX, premoveToY,
4062                                         premovePromoChar);
4063                       }
4064                     }
4065
4066                     /* Usually suppress following prompt */
4067                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4068                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4069                         if (looking_at(buf, &i, "*% ")) {
4070                             savingComment = FALSE;
4071                             suppressKibitz = 0;
4072                         }
4073                     }
4074                     next_out = i;
4075                 } else if (started == STARTED_HOLDINGS) {
4076                     int gamenum;
4077                     char new_piece[MSG_SIZ];
4078                     started = STARTED_NONE;
4079                     parse[parse_pos] = NULLCHAR;
4080                     if (appData.debugMode)
4081                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4082                                                         parse, currentMove);
4083                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4084                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4085                         if (gameInfo.variant == VariantNormal) {
4086                           /* [HGM] We seem to switch variant during a game!
4087                            * Presumably no holdings were displayed, so we have
4088                            * to move the position two files to the right to
4089                            * create room for them!
4090                            */
4091                           VariantClass newVariant;
4092                           switch(gameInfo.boardWidth) { // base guess on board width
4093                                 case 9:  newVariant = VariantShogi; break;
4094                                 case 10: newVariant = VariantGreat; break;
4095                                 default: newVariant = VariantCrazyhouse; break;
4096                           }
4097                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4098                           /* Get a move list just to see the header, which
4099                              will tell us whether this is really bug or zh */
4100                           if (ics_getting_history == H_FALSE) {
4101                             ics_getting_history = H_REQUESTED;
4102                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4103                             SendToICS(str);
4104                           }
4105                         }
4106                         new_piece[0] = NULLCHAR;
4107                         sscanf(parse, "game %d white [%s black [%s <- %s",
4108                                &gamenum, white_holding, black_holding,
4109                                new_piece);
4110                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4111                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4112                         /* [HGM] copy holdings to board holdings area */
4113                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4114                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4115                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4116 #if ZIPPY
4117                         if (appData.zippyPlay && first.initDone) {
4118                             ZippyHoldings(white_holding, black_holding,
4119                                           new_piece);
4120                         }
4121 #endif /*ZIPPY*/
4122                         if (tinyLayout || smallLayout) {
4123                             char wh[16], bh[16];
4124                             PackHolding(wh, white_holding);
4125                             PackHolding(bh, black_holding);
4126                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4127                                     gameInfo.white, gameInfo.black);
4128                         } else {
4129                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4130                                     gameInfo.white, white_holding, _("vs."),
4131                                     gameInfo.black, black_holding);
4132                         }
4133                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4134                         DrawPosition(FALSE, boards[currentMove]);
4135                         DisplayTitle(str);
4136                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4137                         sscanf(parse, "game %d white [%s black [%s <- %s",
4138                                &gamenum, white_holding, black_holding,
4139                                new_piece);
4140                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4141                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4142                         /* [HGM] copy holdings to partner-board holdings area */
4143                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4144                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4145                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4146                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4147                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4148                       }
4149                     }
4150                     /* Suppress following prompt */
4151                     if (looking_at(buf, &i, "*% ")) {
4152                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4153                         savingComment = FALSE;
4154                         suppressKibitz = 0;
4155                     }
4156                     next_out = i;
4157                 }
4158                 continue;
4159             }
4160
4161             i++;                /* skip unparsed character and loop back */
4162         }
4163
4164         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4165 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4166 //          SendToPlayer(&buf[next_out], i - next_out);
4167             started != STARTED_HOLDINGS && leftover_start > next_out) {
4168             SendToPlayer(&buf[next_out], leftover_start - next_out);
4169             next_out = i;
4170         }
4171
4172         leftover_len = buf_len - leftover_start;
4173         /* if buffer ends with something we couldn't parse,
4174            reparse it after appending the next read */
4175
4176     } else if (count == 0) {
4177         RemoveInputSource(isr);
4178         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4179     } else {
4180         DisplayFatalError(_("Error reading from ICS"), error, 1);
4181     }
4182 }
4183
4184
4185 /* Board style 12 looks like this:
4186
4187    <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
4188
4189  * The "<12> " is stripped before it gets to this routine.  The two
4190  * trailing 0's (flip state and clock ticking) are later addition, and
4191  * some chess servers may not have them, or may have only the first.
4192  * Additional trailing fields may be added in the future.
4193  */
4194
4195 #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"
4196
4197 #define RELATION_OBSERVING_PLAYED    0
4198 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4199 #define RELATION_PLAYING_MYMOVE      1
4200 #define RELATION_PLAYING_NOTMYMOVE  -1
4201 #define RELATION_EXAMINING           2
4202 #define RELATION_ISOLATED_BOARD     -3
4203 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4204
4205 void
4206 ParseBoard12 (char *string)
4207 {
4208 #if ZIPPY
4209     int i, takeback;
4210     char *bookHit = NULL; // [HGM] book
4211 #endif
4212     GameMode newGameMode;
4213     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4214     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4215     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4216     char to_play, board_chars[200];
4217     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4218     char black[32], white[32];
4219     Board board;
4220     int prevMove = currentMove;
4221     int ticking = 2;
4222     ChessMove moveType;
4223     int fromX, fromY, toX, toY;
4224     char promoChar;
4225     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4226     Boolean weird = FALSE, reqFlag = FALSE;
4227
4228     fromX = fromY = toX = toY = -1;
4229
4230     newGame = FALSE;
4231
4232     if (appData.debugMode)
4233       fprintf(debugFP, "Parsing board: %s\n", string);
4234
4235     move_str[0] = NULLCHAR;
4236     elapsed_time[0] = NULLCHAR;
4237     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4238         int  i = 0, j;
4239         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4240             if(string[i] == ' ') { ranks++; files = 0; }
4241             else files++;
4242             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4243             i++;
4244         }
4245         for(j = 0; j <i; j++) board_chars[j] = string[j];
4246         board_chars[i] = '\0';
4247         string += i + 1;
4248     }
4249     n = sscanf(string, PATTERN, &to_play, &double_push,
4250                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4251                &gamenum, white, black, &relation, &basetime, &increment,
4252                &white_stren, &black_stren, &white_time, &black_time,
4253                &moveNum, str, elapsed_time, move_str, &ics_flip,
4254                &ticking);
4255
4256     if (n < 21) {
4257         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4258         DisplayError(str, 0);
4259         return;
4260     }
4261
4262     /* Convert the move number to internal form */
4263     moveNum = (moveNum - 1) * 2;
4264     if (to_play == 'B') moveNum++;
4265     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4266       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4267                         0, 1);
4268       return;
4269     }
4270
4271     switch (relation) {
4272       case RELATION_OBSERVING_PLAYED:
4273       case RELATION_OBSERVING_STATIC:
4274         if (gamenum == -1) {
4275             /* Old ICC buglet */
4276             relation = RELATION_OBSERVING_STATIC;
4277         }
4278         newGameMode = IcsObserving;
4279         break;
4280       case RELATION_PLAYING_MYMOVE:
4281       case RELATION_PLAYING_NOTMYMOVE:
4282         newGameMode =
4283           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4284             IcsPlayingWhite : IcsPlayingBlack;
4285         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4286         break;
4287       case RELATION_EXAMINING:
4288         newGameMode = IcsExamining;
4289         break;
4290       case RELATION_ISOLATED_BOARD:
4291       default:
4292         /* Just display this board.  If user was doing something else,
4293            we will forget about it until the next board comes. */
4294         newGameMode = IcsIdle;
4295         break;
4296       case RELATION_STARTING_POSITION:
4297         newGameMode = gameMode;
4298         break;
4299     }
4300
4301     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4302         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4303          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4304       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4305       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4306       static int lastBgGame = -1;
4307       char *toSqr;
4308       for (k = 0; k < ranks; k++) {
4309         for (j = 0; j < files; j++)
4310           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4311         if(gameInfo.holdingsWidth > 1) {
4312              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4313              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4314         }
4315       }
4316       CopyBoard(partnerBoard, board);
4317       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4318         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4319         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4320       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4321       if(toSqr = strchr(str, '-')) {
4322         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4323         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4324       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4325       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4326       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4327       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4328       if(twoBoards) {
4329           DisplayWhiteClock(white_time*fac, to_play == 'W');
4330           DisplayBlackClock(black_time*fac, to_play != 'W');
4331           activePartner = to_play;
4332           if(gamenum != lastBgGame) {
4333               char buf[MSG_SIZ];
4334               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4335               DisplayTitle(buf);
4336           }
4337           lastBgGame = gamenum;
4338           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4339                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4340       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4341                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4342       if(!twoBoards) DisplayMessage(partnerStatus, "");
4343         partnerBoardValid = TRUE;
4344       return;
4345     }
4346
4347     if(appData.dualBoard && appData.bgObserve) {
4348         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4349             SendToICS(ics_prefix), SendToICS("pobserve\n");
4350         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4351             char buf[MSG_SIZ];
4352             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4353             SendToICS(buf);
4354         }
4355     }
4356
4357     /* Modify behavior for initial board display on move listing
4358        of wild games.
4359        */
4360     switch (ics_getting_history) {
4361       case H_FALSE:
4362       case H_REQUESTED:
4363         break;
4364       case H_GOT_REQ_HEADER:
4365       case H_GOT_UNREQ_HEADER:
4366         /* This is the initial position of the current game */
4367         gamenum = ics_gamenum;
4368         moveNum = 0;            /* old ICS bug workaround */
4369         if (to_play == 'B') {
4370           startedFromSetupPosition = TRUE;
4371           blackPlaysFirst = TRUE;
4372           moveNum = 1;
4373           if (forwardMostMove == 0) forwardMostMove = 1;
4374           if (backwardMostMove == 0) backwardMostMove = 1;
4375           if (currentMove == 0) currentMove = 1;
4376         }
4377         newGameMode = gameMode;
4378         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4379         break;
4380       case H_GOT_UNWANTED_HEADER:
4381         /* This is an initial board that we don't want */
4382         return;
4383       case H_GETTING_MOVES:
4384         /* Should not happen */
4385         DisplayError(_("Error gathering move list: extra board"), 0);
4386         ics_getting_history = H_FALSE;
4387         return;
4388     }
4389
4390    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4391                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4392                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4393      /* [HGM] We seem to have switched variant unexpectedly
4394       * Try to guess new variant from board size
4395       */
4396           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4397           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4398           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4399           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4400           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4401           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4402           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4403           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4404           /* Get a move list just to see the header, which
4405              will tell us whether this is really bug or zh */
4406           if (ics_getting_history == H_FALSE) {
4407             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4408             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4409             SendToICS(str);
4410           }
4411     }
4412
4413     /* Take action if this is the first board of a new game, or of a
4414        different game than is currently being displayed.  */
4415     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4416         relation == RELATION_ISOLATED_BOARD) {
4417
4418         /* Forget the old game and get the history (if any) of the new one */
4419         if (gameMode != BeginningOfGame) {
4420           Reset(TRUE, TRUE);
4421         }
4422         newGame = TRUE;
4423         if (appData.autoRaiseBoard) BoardToTop();
4424         prevMove = -3;
4425         if (gamenum == -1) {
4426             newGameMode = IcsIdle;
4427         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4428                    appData.getMoveList && !reqFlag) {
4429             /* Need to get game history */
4430             ics_getting_history = H_REQUESTED;
4431             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4432             SendToICS(str);
4433         }
4434
4435         /* Initially flip the board to have black on the bottom if playing
4436            black or if the ICS flip flag is set, but let the user change
4437            it with the Flip View button. */
4438         flipView = appData.autoFlipView ?
4439           (newGameMode == IcsPlayingBlack) || ics_flip :
4440           appData.flipView;
4441
4442         /* Done with values from previous mode; copy in new ones */
4443         gameMode = newGameMode;
4444         ModeHighlight();
4445         ics_gamenum = gamenum;
4446         if (gamenum == gs_gamenum) {
4447             int klen = strlen(gs_kind);
4448             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4449             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4450             gameInfo.event = StrSave(str);
4451         } else {
4452             gameInfo.event = StrSave("ICS game");
4453         }
4454         gameInfo.site = StrSave(appData.icsHost);
4455         gameInfo.date = PGNDate();
4456         gameInfo.round = StrSave("-");
4457         gameInfo.white = StrSave(white);
4458         gameInfo.black = StrSave(black);
4459         timeControl = basetime * 60 * 1000;
4460         timeControl_2 = 0;
4461         timeIncrement = increment * 1000;
4462         movesPerSession = 0;
4463         gameInfo.timeControl = TimeControlTagValue();
4464         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4465   if (appData.debugMode) {
4466     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4467     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4468     setbuf(debugFP, NULL);
4469   }
4470
4471         gameInfo.outOfBook = NULL;
4472
4473         /* Do we have the ratings? */
4474         if (strcmp(player1Name, white) == 0 &&
4475             strcmp(player2Name, black) == 0) {
4476             if (appData.debugMode)
4477               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4478                       player1Rating, player2Rating);
4479             gameInfo.whiteRating = player1Rating;
4480             gameInfo.blackRating = player2Rating;
4481         } else if (strcmp(player2Name, white) == 0 &&
4482                    strcmp(player1Name, black) == 0) {
4483             if (appData.debugMode)
4484               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4485                       player2Rating, player1Rating);
4486             gameInfo.whiteRating = player2Rating;
4487             gameInfo.blackRating = player1Rating;
4488         }
4489         player1Name[0] = player2Name[0] = NULLCHAR;
4490
4491         /* Silence shouts if requested */
4492         if (appData.quietPlay &&
4493             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4494             SendToICS(ics_prefix);
4495             SendToICS("set shout 0\n");
4496         }
4497     }
4498
4499     /* Deal with midgame name changes */
4500     if (!newGame) {
4501         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4502             if (gameInfo.white) free(gameInfo.white);
4503             gameInfo.white = StrSave(white);
4504         }
4505         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4506             if (gameInfo.black) free(gameInfo.black);
4507             gameInfo.black = StrSave(black);
4508         }
4509     }
4510
4511     /* Throw away game result if anything actually changes in examine mode */
4512     if (gameMode == IcsExamining && !newGame) {
4513         gameInfo.result = GameUnfinished;
4514         if (gameInfo.resultDetails != NULL) {
4515             free(gameInfo.resultDetails);
4516             gameInfo.resultDetails = NULL;
4517         }
4518     }
4519
4520     /* In pausing && IcsExamining mode, we ignore boards coming
4521        in if they are in a different variation than we are. */
4522     if (pauseExamInvalid) return;
4523     if (pausing && gameMode == IcsExamining) {
4524         if (moveNum <= pauseExamForwardMostMove) {
4525             pauseExamInvalid = TRUE;
4526             forwardMostMove = pauseExamForwardMostMove;
4527             return;
4528         }
4529     }
4530
4531   if (appData.debugMode) {
4532     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4533   }
4534     /* Parse the board */
4535     for (k = 0; k < ranks; k++) {
4536       for (j = 0; j < files; j++)
4537         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4538       if(gameInfo.holdingsWidth > 1) {
4539            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4540            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4541       }
4542     }
4543     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4544       board[5][BOARD_RGHT+1] = WhiteAngel;
4545       board[6][BOARD_RGHT+1] = WhiteMarshall;
4546       board[1][0] = BlackMarshall;
4547       board[2][0] = BlackAngel;
4548       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4549     }
4550     CopyBoard(boards[moveNum], board);
4551     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4552     if (moveNum == 0) {
4553         startedFromSetupPosition =
4554           !CompareBoards(board, initialPosition);
4555         if(startedFromSetupPosition)
4556             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4557     }
4558
4559     /* [HGM] Set castling rights. Take the outermost Rooks,
4560        to make it also work for FRC opening positions. Note that board12
4561        is really defective for later FRC positions, as it has no way to
4562        indicate which Rook can castle if they are on the same side of King.
4563        For the initial position we grant rights to the outermost Rooks,
4564        and remember thos rights, and we then copy them on positions
4565        later in an FRC game. This means WB might not recognize castlings with
4566        Rooks that have moved back to their original position as illegal,
4567        but in ICS mode that is not its job anyway.
4568     */
4569     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4570     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4571
4572         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4573             if(board[0][i] == WhiteRook) j = i;
4574         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4575         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4576             if(board[0][i] == WhiteRook) j = i;
4577         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4578         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4579             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4580         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4581         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4583         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4584
4585         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4586         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4587         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4588             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4590             if(board[BOARD_HEIGHT-1][k] == bKing)
4591                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4592         if(gameInfo.variant == VariantTwoKings) {
4593             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4594             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4595             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4596         }
4597     } else { int r;
4598         r = boards[moveNum][CASTLING][0] = initialRights[0];
4599         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4600         r = boards[moveNum][CASTLING][1] = initialRights[1];
4601         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4602         r = boards[moveNum][CASTLING][3] = initialRights[3];
4603         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4604         r = boards[moveNum][CASTLING][4] = initialRights[4];
4605         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4606         /* wildcastle kludge: always assume King has rights */
4607         r = boards[moveNum][CASTLING][2] = initialRights[2];
4608         r = boards[moveNum][CASTLING][5] = initialRights[5];
4609     }
4610     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4611     boards[moveNum][EP_STATUS] = EP_NONE;
4612     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4613     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4614     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4615
4616
4617     if (ics_getting_history == H_GOT_REQ_HEADER ||
4618         ics_getting_history == H_GOT_UNREQ_HEADER) {
4619         /* This was an initial position from a move list, not
4620            the current position */
4621         return;
4622     }
4623
4624     /* Update currentMove and known move number limits */
4625     newMove = newGame || moveNum > forwardMostMove;
4626
4627     if (newGame) {
4628         forwardMostMove = backwardMostMove = currentMove = moveNum;
4629         if (gameMode == IcsExamining && moveNum == 0) {
4630           /* Workaround for ICS limitation: we are not told the wild
4631              type when starting to examine a game.  But if we ask for
4632              the move list, the move list header will tell us */
4633             ics_getting_history = H_REQUESTED;
4634             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4635             SendToICS(str);
4636         }
4637     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4638                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4639 #if ZIPPY
4640         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4641         /* [HGM] applied this also to an engine that is silently watching        */
4642         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4643             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4644             gameInfo.variant == currentlyInitializedVariant) {
4645           takeback = forwardMostMove - moveNum;
4646           for (i = 0; i < takeback; i++) {
4647             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4648             SendToProgram("undo\n", &first);
4649           }
4650         }
4651 #endif
4652
4653         forwardMostMove = moveNum;
4654         if (!pausing || currentMove > forwardMostMove)
4655           currentMove = forwardMostMove;
4656     } else {
4657         /* New part of history that is not contiguous with old part */
4658         if (pausing && gameMode == IcsExamining) {
4659             pauseExamInvalid = TRUE;
4660             forwardMostMove = pauseExamForwardMostMove;
4661             return;
4662         }
4663         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4664 #if ZIPPY
4665             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4666                 // [HGM] when we will receive the move list we now request, it will be
4667                 // fed to the engine from the first move on. So if the engine is not
4668                 // in the initial position now, bring it there.
4669                 InitChessProgram(&first, 0);
4670             }
4671 #endif
4672             ics_getting_history = H_REQUESTED;
4673             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4674             SendToICS(str);
4675         }
4676         forwardMostMove = backwardMostMove = currentMove = moveNum;
4677     }
4678
4679     /* Update the clocks */
4680     if (strchr(elapsed_time, '.')) {
4681       /* Time is in ms */
4682       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4683       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4684     } else {
4685       /* Time is in seconds */
4686       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4687       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4688     }
4689
4690
4691 #if ZIPPY
4692     if (appData.zippyPlay && newGame &&
4693         gameMode != IcsObserving && gameMode != IcsIdle &&
4694         gameMode != IcsExamining)
4695       ZippyFirstBoard(moveNum, basetime, increment);
4696 #endif
4697
4698     /* Put the move on the move list, first converting
4699        to canonical algebraic form. */
4700     if (moveNum > 0) {
4701   if (appData.debugMode) {
4702     int f = forwardMostMove;
4703     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4704             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4705             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4706     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4707     fprintf(debugFP, "moveNum = %d\n", moveNum);
4708     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4709     setbuf(debugFP, NULL);
4710   }
4711         if (moveNum <= backwardMostMove) {
4712             /* We don't know what the board looked like before
4713                this move.  Punt. */
4714           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4715             strcat(parseList[moveNum - 1], " ");
4716             strcat(parseList[moveNum - 1], elapsed_time);
4717             moveList[moveNum - 1][0] = NULLCHAR;
4718         } else if (strcmp(move_str, "none") == 0) {
4719             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4720             /* Again, we don't know what the board looked like;
4721                this is really the start of the game. */
4722             parseList[moveNum - 1][0] = NULLCHAR;
4723             moveList[moveNum - 1][0] = NULLCHAR;
4724             backwardMostMove = moveNum;
4725             startedFromSetupPosition = TRUE;
4726             fromX = fromY = toX = toY = -1;
4727         } else {
4728           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4729           //                 So we parse the long-algebraic move string in stead of the SAN move
4730           int valid; char buf[MSG_SIZ], *prom;
4731
4732           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4733                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4734           // str looks something like "Q/a1-a2"; kill the slash
4735           if(str[1] == '/')
4736             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4737           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4738           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4739                 strcat(buf, prom); // long move lacks promo specification!
4740           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4741                 if(appData.debugMode)
4742                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4743                 safeStrCpy(move_str, buf, MSG_SIZ);
4744           }
4745           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4746                                 &fromX, &fromY, &toX, &toY, &promoChar)
4747                || ParseOneMove(buf, moveNum - 1, &moveType,
4748                                 &fromX, &fromY, &toX, &toY, &promoChar);
4749           // end of long SAN patch
4750           if (valid) {
4751             (void) CoordsToAlgebraic(boards[moveNum - 1],
4752                                      PosFlags(moveNum - 1),
4753                                      fromY, fromX, toY, toX, promoChar,
4754                                      parseList[moveNum-1]);
4755             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4756               case MT_NONE:
4757               case MT_STALEMATE:
4758               default:
4759                 break;
4760               case MT_CHECK:
4761                 if(gameInfo.variant != VariantShogi)
4762                     strcat(parseList[moveNum - 1], "+");
4763                 break;
4764               case MT_CHECKMATE:
4765               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4766                 strcat(parseList[moveNum - 1], "#");
4767                 break;
4768             }
4769             strcat(parseList[moveNum - 1], " ");
4770             strcat(parseList[moveNum - 1], elapsed_time);
4771             /* currentMoveString is set as a side-effect of ParseOneMove */
4772             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4773             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4774             strcat(moveList[moveNum - 1], "\n");
4775
4776             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4777                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4778               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4779                 ChessSquare old, new = boards[moveNum][k][j];
4780                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4781                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4782                   if(old == new) continue;
4783                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4784                   else if(new == WhiteWazir || new == BlackWazir) {
4785                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4786                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4787                       else boards[moveNum][k][j] = old; // preserve type of Gold
4788                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4789                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4790               }
4791           } else {
4792             /* Move from ICS was illegal!?  Punt. */
4793             if (appData.debugMode) {
4794               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4795               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4796             }
4797             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4798             strcat(parseList[moveNum - 1], " ");
4799             strcat(parseList[moveNum - 1], elapsed_time);
4800             moveList[moveNum - 1][0] = NULLCHAR;
4801             fromX = fromY = toX = toY = -1;
4802           }
4803         }
4804   if (appData.debugMode) {
4805     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4806     setbuf(debugFP, NULL);
4807   }
4808
4809 #if ZIPPY
4810         /* Send move to chess program (BEFORE animating it). */
4811         if (appData.zippyPlay && !newGame && newMove &&
4812            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4813
4814             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4815                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4816                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4817                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4818                             move_str);
4819                     DisplayError(str, 0);
4820                 } else {
4821                     if (first.sendTime) {
4822                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4823                     }
4824                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4825                     if (firstMove && !bookHit) {
4826                         firstMove = FALSE;
4827                         if (first.useColors) {
4828                           SendToProgram(gameMode == IcsPlayingWhite ?
4829                                         "white\ngo\n" :
4830                                         "black\ngo\n", &first);
4831                         } else {
4832                           SendToProgram("go\n", &first);
4833                         }
4834                         first.maybeThinking = TRUE;
4835                     }
4836                 }
4837             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4838               if (moveList[moveNum - 1][0] == NULLCHAR) {
4839                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4840                 DisplayError(str, 0);
4841               } else {
4842                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4843                 SendMoveToProgram(moveNum - 1, &first);
4844               }
4845             }
4846         }
4847 #endif
4848     }
4849
4850     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4851         /* If move comes from a remote source, animate it.  If it
4852            isn't remote, it will have already been animated. */
4853         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4854             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4855         }
4856         if (!pausing && appData.highlightLastMove) {
4857             SetHighlights(fromX, fromY, toX, toY);
4858         }
4859     }
4860
4861     /* Start the clocks */
4862     whiteFlag = blackFlag = FALSE;
4863     appData.clockMode = !(basetime == 0 && increment == 0);
4864     if (ticking == 0) {
4865       ics_clock_paused = TRUE;
4866       StopClocks();
4867     } else if (ticking == 1) {
4868       ics_clock_paused = FALSE;
4869     }
4870     if (gameMode == IcsIdle ||
4871         relation == RELATION_OBSERVING_STATIC ||
4872         relation == RELATION_EXAMINING ||
4873         ics_clock_paused)
4874       DisplayBothClocks();
4875     else
4876       StartClocks();
4877
4878     /* Display opponents and material strengths */
4879     if (gameInfo.variant != VariantBughouse &&
4880         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4881         if (tinyLayout || smallLayout) {
4882             if(gameInfo.variant == VariantNormal)
4883               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4885                     basetime, increment);
4886             else
4887               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4888                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4889                     basetime, increment, (int) gameInfo.variant);
4890         } else {
4891             if(gameInfo.variant == VariantNormal)
4892               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4893                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894                     basetime, increment);
4895             else
4896               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4897                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4898                     basetime, increment, VariantName(gameInfo.variant));
4899         }
4900         DisplayTitle(str);
4901   if (appData.debugMode) {
4902     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4903   }
4904     }
4905
4906
4907     /* Display the board */
4908     if (!pausing && !appData.noGUI) {
4909
4910       if (appData.premove)
4911           if (!gotPremove ||
4912              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4913              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4914               ClearPremoveHighlights();
4915
4916       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4917         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4918       DrawPosition(j, boards[currentMove]);
4919
4920       DisplayMove(moveNum - 1);
4921       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4922             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4923               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4924         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4925       }
4926     }
4927
4928     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4929 #if ZIPPY
4930     if(bookHit) { // [HGM] book: simulate book reply
4931         static char bookMove[MSG_SIZ]; // a bit generous?
4932
4933         programStats.nodes = programStats.depth = programStats.time =
4934         programStats.score = programStats.got_only_move = 0;
4935         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4936
4937         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4938         strcat(bookMove, bookHit);
4939         HandleMachineMove(bookMove, &first);
4940     }
4941 #endif
4942 }
4943
4944 void
4945 GetMoveListEvent ()
4946 {
4947     char buf[MSG_SIZ];
4948     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4949         ics_getting_history = H_REQUESTED;
4950         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4951         SendToICS(buf);
4952     }
4953 }
4954
4955 void
4956 SendToBoth (char *msg)
4957 {   // to make it easy to keep two engines in step in dual analysis
4958     SendToProgram(msg, &first);
4959     if(second.analyzing) SendToProgram(msg, &second);
4960 }
4961
4962 void
4963 AnalysisPeriodicEvent (int force)
4964 {
4965     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4966          && !force) || !appData.periodicUpdates)
4967       return;
4968
4969     /* Send . command to Crafty to collect stats */
4970     SendToBoth(".\n");
4971
4972     /* Don't send another until we get a response (this makes
4973        us stop sending to old Crafty's which don't understand
4974        the "." command (sending illegal cmds resets node count & time,
4975        which looks bad)) */
4976     programStats.ok_to_send = 0;
4977 }
4978
4979 void
4980 ics_update_width (int new_width)
4981 {
4982         ics_printf("set width %d\n", new_width);
4983 }
4984
4985 void
4986 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4987 {
4988     char buf[MSG_SIZ];
4989
4990     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4991         // null move in variant where engine does not understand it (for analysis purposes)
4992         SendBoard(cps, moveNum + 1); // send position after move in stead.
4993         return;
4994     }
4995     if (cps->useUsermove) {
4996       SendToProgram("usermove ", cps);
4997     }
4998     if (cps->useSAN) {
4999       char *space;
5000       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5001         int len = space - parseList[moveNum];
5002         memcpy(buf, parseList[moveNum], len);
5003         buf[len++] = '\n';
5004         buf[len] = NULLCHAR;
5005       } else {
5006         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5007       }
5008       SendToProgram(buf, cps);
5009     } else {
5010       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5011         AlphaRank(moveList[moveNum], 4);
5012         SendToProgram(moveList[moveNum], cps);
5013         AlphaRank(moveList[moveNum], 4); // and back
5014       } else
5015       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5016        * the engine. It would be nice to have a better way to identify castle
5017        * moves here. */
5018       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5019                                                                          && cps->useOOCastle) {
5020         int fromX = moveList[moveNum][0] - AAA;
5021         int fromY = moveList[moveNum][1] - ONE;
5022         int toX = moveList[moveNum][2] - AAA;
5023         int toY = moveList[moveNum][3] - ONE;
5024         if((boards[moveNum][fromY][fromX] == WhiteKing
5025             && boards[moveNum][toY][toX] == WhiteRook)
5026            || (boards[moveNum][fromY][fromX] == BlackKing
5027                && boards[moveNum][toY][toX] == BlackRook)) {
5028           if(toX > fromX) SendToProgram("O-O\n", cps);
5029           else SendToProgram("O-O-O\n", cps);
5030         }
5031         else SendToProgram(moveList[moveNum], cps);
5032       } else
5033       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5034         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5035           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5036           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5037                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5038         } else
5039           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5040                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5041         SendToProgram(buf, cps);
5042       }
5043       else SendToProgram(moveList[moveNum], cps);
5044       /* End of additions by Tord */
5045     }
5046
5047     /* [HGM] setting up the opening has brought engine in force mode! */
5048     /*       Send 'go' if we are in a mode where machine should play. */
5049     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5050         (gameMode == TwoMachinesPlay   ||
5051 #if ZIPPY
5052          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5053 #endif
5054          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5055         SendToProgram("go\n", cps);
5056   if (appData.debugMode) {
5057     fprintf(debugFP, "(extra)\n");
5058   }
5059     }
5060     setboardSpoiledMachineBlack = 0;
5061 }
5062
5063 void
5064 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5065 {
5066     char user_move[MSG_SIZ];
5067     char suffix[4];
5068
5069     if(gameInfo.variant == VariantSChess && promoChar) {
5070         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5071         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5072     } else suffix[0] = NULLCHAR;
5073
5074     switch (moveType) {
5075       default:
5076         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5077                 (int)moveType, fromX, fromY, toX, toY);
5078         DisplayError(user_move + strlen("say "), 0);
5079         break;
5080       case WhiteKingSideCastle:
5081       case BlackKingSideCastle:
5082       case WhiteQueenSideCastleWild:
5083       case BlackQueenSideCastleWild:
5084       /* PUSH Fabien */
5085       case WhiteHSideCastleFR:
5086       case BlackHSideCastleFR:
5087       /* POP Fabien */
5088         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5089         break;
5090       case WhiteQueenSideCastle:
5091       case BlackQueenSideCastle:
5092       case WhiteKingSideCastleWild:
5093       case BlackKingSideCastleWild:
5094       /* PUSH Fabien */
5095       case WhiteASideCastleFR:
5096       case BlackASideCastleFR:
5097       /* POP Fabien */
5098         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5099         break;
5100       case WhiteNonPromotion:
5101       case BlackNonPromotion:
5102         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5103         break;
5104       case WhitePromotion:
5105       case BlackPromotion:
5106         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5107            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5108           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5109                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110                 PieceToChar(WhiteFerz));
5111         else if(gameInfo.variant == VariantGreat)
5112           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114                 PieceToChar(WhiteMan));
5115         else
5116           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5117                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5118                 promoChar);
5119         break;
5120       case WhiteDrop:
5121       case BlackDrop:
5122       drop:
5123         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5124                  ToUpper(PieceToChar((ChessSquare) fromX)),
5125                  AAA + toX, ONE + toY);
5126         break;
5127       case IllegalMove:  /* could be a variant we don't quite understand */
5128         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5129       case NormalMove:
5130       case WhiteCapturesEnPassant:
5131       case BlackCapturesEnPassant:
5132         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5133                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5134         break;
5135     }
5136     SendToICS(user_move);
5137     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5138         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5139 }
5140
5141 void
5142 UploadGameEvent ()
5143 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5144     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5145     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5146     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5147       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5148       return;
5149     }
5150     if(gameMode != IcsExamining) { // is this ever not the case?
5151         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5152
5153         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5154           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5155         } else { // on FICS we must first go to general examine mode
5156           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5157         }
5158         if(gameInfo.variant != VariantNormal) {
5159             // try figure out wild number, as xboard names are not always valid on ICS
5160             for(i=1; i<=36; i++) {
5161               snprintf(buf, MSG_SIZ, "wild/%d", i);
5162                 if(StringToVariant(buf) == gameInfo.variant) break;
5163             }
5164             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5165             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5166             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5167         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5168         SendToICS(ics_prefix);
5169         SendToICS(buf);
5170         if(startedFromSetupPosition || backwardMostMove != 0) {
5171           fen = PositionToFEN(backwardMostMove, NULL, 1);
5172           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5173             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5174             SendToICS(buf);
5175           } else { // FICS: everything has to set by separate bsetup commands
5176             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5177             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5178             SendToICS(buf);
5179             if(!WhiteOnMove(backwardMostMove)) {
5180                 SendToICS("bsetup tomove black\n");
5181             }
5182             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5183             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5184             SendToICS(buf);
5185             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5186             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5187             SendToICS(buf);
5188             i = boards[backwardMostMove][EP_STATUS];
5189             if(i >= 0) { // set e.p.
5190               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5191                 SendToICS(buf);
5192             }
5193             bsetup++;
5194           }
5195         }
5196       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5197             SendToICS("bsetup done\n"); // switch to normal examining.
5198     }
5199     for(i = backwardMostMove; i<last; i++) {
5200         char buf[20];
5201         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5202         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5203             int len = strlen(moveList[i]);
5204             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5205             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5206         }
5207         SendToICS(buf);
5208     }
5209     SendToICS(ics_prefix);
5210     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5211 }
5212
5213 void
5214 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5215 {
5216     if (rf == DROP_RANK) {
5217       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5218       sprintf(move, "%c@%c%c\n",
5219                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5220     } else {
5221         if (promoChar == 'x' || promoChar == NULLCHAR) {
5222           sprintf(move, "%c%c%c%c\n",
5223                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5224         } else {
5225             sprintf(move, "%c%c%c%c%c\n",
5226                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5227         }
5228     }
5229 }
5230
5231 void
5232 ProcessICSInitScript (FILE *f)
5233 {
5234     char buf[MSG_SIZ];
5235
5236     while (fgets(buf, MSG_SIZ, f)) {
5237         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5238     }
5239
5240     fclose(f);
5241 }
5242
5243
5244 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5245 static ClickType lastClickType;
5246
5247 void
5248 Sweep (int step)
5249 {
5250     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5251     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5252     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5253     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5254     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5255     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5256     do {
5257         promoSweep -= step;
5258         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5259         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5260         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5261         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5262         if(!step) step = -1;
5263     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5264             appData.testLegality && (promoSweep == king ||
5265             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5266     if(toX >= 0) {
5267         int victim = boards[currentMove][toY][toX];
5268         boards[currentMove][toY][toX] = promoSweep;
5269         DrawPosition(FALSE, boards[currentMove]);
5270         boards[currentMove][toY][toX] = victim;
5271     } else
5272     ChangeDragPiece(promoSweep);
5273 }
5274
5275 int
5276 PromoScroll (int x, int y)
5277 {
5278   int step = 0;
5279
5280   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5281   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5282   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5283   if(!step) return FALSE;
5284   lastX = x; lastY = y;
5285   if((promoSweep < BlackPawn) == flipView) step = -step;
5286   if(step > 0) selectFlag = 1;
5287   if(!selectFlag) Sweep(step);
5288   return FALSE;
5289 }
5290
5291 void
5292 NextPiece (int step)
5293 {
5294     ChessSquare piece = boards[currentMove][toY][toX];
5295     do {
5296         pieceSweep -= step;
5297         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5298         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5299         if(!step) step = -1;
5300     } while(PieceToChar(pieceSweep) == '.');
5301     boards[currentMove][toY][toX] = pieceSweep;
5302     DrawPosition(FALSE, boards[currentMove]);
5303     boards[currentMove][toY][toX] = piece;
5304 }
5305 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5306 void
5307 AlphaRank (char *move, int n)
5308 {
5309 //    char *p = move, c; int x, y;
5310
5311     if (appData.debugMode) {
5312         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5313     }
5314
5315     if(move[1]=='*' &&
5316        move[2]>='0' && move[2]<='9' &&
5317        move[3]>='a' && move[3]<='x'    ) {
5318         move[1] = '@';
5319         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5320         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5321     } else
5322     if(move[0]>='0' && move[0]<='9' &&
5323        move[1]>='a' && move[1]<='x' &&
5324        move[2]>='0' && move[2]<='9' &&
5325        move[3]>='a' && move[3]<='x'    ) {
5326         /* input move, Shogi -> normal */
5327         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5328         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5329         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5330         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5331     } else
5332     if(move[1]=='@' &&
5333        move[3]>='0' && move[3]<='9' &&
5334        move[2]>='a' && move[2]<='x'    ) {
5335         move[1] = '*';
5336         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5337         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5338     } else
5339     if(
5340        move[0]>='a' && move[0]<='x' &&
5341        move[3]>='0' && move[3]<='9' &&
5342        move[2]>='a' && move[2]<='x'    ) {
5343          /* output move, normal -> Shogi */
5344         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5345         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5346         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5347         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5348         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5349     }
5350     if (appData.debugMode) {
5351         fprintf(debugFP, "   out = '%s'\n", move);
5352     }
5353 }
5354
5355 char yy_textstr[8000];
5356
5357 /* Parser for moves from gnuchess, ICS, or user typein box */
5358 Boolean
5359 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5360 {
5361     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5362
5363     switch (*moveType) {
5364       case WhitePromotion:
5365       case BlackPromotion:
5366       case WhiteNonPromotion:
5367       case BlackNonPromotion:
5368       case NormalMove:
5369       case WhiteCapturesEnPassant:
5370       case BlackCapturesEnPassant:
5371       case WhiteKingSideCastle:
5372       case WhiteQueenSideCastle:
5373       case BlackKingSideCastle:
5374       case BlackQueenSideCastle:
5375       case WhiteKingSideCastleWild:
5376       case WhiteQueenSideCastleWild:
5377       case BlackKingSideCastleWild:
5378       case BlackQueenSideCastleWild:
5379       /* Code added by Tord: */
5380       case WhiteHSideCastleFR:
5381       case WhiteASideCastleFR:
5382       case BlackHSideCastleFR:
5383       case BlackASideCastleFR:
5384       /* End of code added by Tord */
5385       case IllegalMove:         /* bug or odd chess variant */
5386         *fromX = currentMoveString[0] - AAA;
5387         *fromY = currentMoveString[1] - ONE;
5388         *toX = currentMoveString[2] - AAA;
5389         *toY = currentMoveString[3] - ONE;
5390         *promoChar = currentMoveString[4];
5391         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5392             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5393     if (appData.debugMode) {
5394         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5395     }
5396             *fromX = *fromY = *toX = *toY = 0;
5397             return FALSE;
5398         }
5399         if (appData.testLegality) {
5400           return (*moveType != IllegalMove);
5401         } else {
5402           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5403                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5404         }
5405
5406       case WhiteDrop:
5407       case BlackDrop:
5408         *fromX = *moveType == WhiteDrop ?
5409           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5410           (int) CharToPiece(ToLower(currentMoveString[0]));
5411         *fromY = DROP_RANK;
5412         *toX = currentMoveString[2] - AAA;
5413         *toY = currentMoveString[3] - ONE;
5414         *promoChar = NULLCHAR;
5415         return TRUE;
5416
5417       case AmbiguousMove:
5418       case ImpossibleMove:
5419       case EndOfFile:
5420       case ElapsedTime:
5421       case Comment:
5422       case PGNTag:
5423       case NAG:
5424       case WhiteWins:
5425       case BlackWins:
5426       case GameIsDrawn:
5427       default:
5428     if (appData.debugMode) {
5429         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5430     }
5431         /* bug? */
5432         *fromX = *fromY = *toX = *toY = 0;
5433         *promoChar = NULLCHAR;
5434         return FALSE;
5435     }
5436 }
5437
5438 Boolean pushed = FALSE;
5439 char *lastParseAttempt;
5440
5441 void
5442 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5443 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5444   int fromX, fromY, toX, toY; char promoChar;
5445   ChessMove moveType;
5446   Boolean valid;
5447   int nr = 0;
5448
5449   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5450   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5451     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5452     pushed = TRUE;
5453   }
5454   endPV = forwardMostMove;
5455   do {
5456     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5457     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5458     lastParseAttempt = pv;
5459     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5460     if(!valid && nr == 0 &&
5461        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5462         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5463         // Hande case where played move is different from leading PV move
5464         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5465         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5466         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5467         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5468           endPV += 2; // if position different, keep this
5469           moveList[endPV-1][0] = fromX + AAA;
5470           moveList[endPV-1][1] = fromY + ONE;
5471           moveList[endPV-1][2] = toX + AAA;
5472           moveList[endPV-1][3] = toY + ONE;
5473           parseList[endPV-1][0] = NULLCHAR;
5474           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5475         }
5476       }
5477     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5478     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5479     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5480     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5481         valid++; // allow comments in PV
5482         continue;
5483     }
5484     nr++;
5485     if(endPV+1 > framePtr) break; // no space, truncate
5486     if(!valid) break;
5487     endPV++;
5488     CopyBoard(boards[endPV], boards[endPV-1]);
5489     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5490     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5491     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5492     CoordsToAlgebraic(boards[endPV - 1],
5493                              PosFlags(endPV - 1),
5494                              fromY, fromX, toY, toX, promoChar,
5495                              parseList[endPV - 1]);
5496   } while(valid);
5497   if(atEnd == 2) return; // used hidden, for PV conversion
5498   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5499   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5500   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5501                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5502   DrawPosition(TRUE, boards[currentMove]);
5503 }
5504
5505 int
5506 MultiPV (ChessProgramState *cps)
5507 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5508         int i;
5509         for(i=0; i<cps->nrOptions; i++)
5510             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5511                 return i;
5512         return -1;
5513 }
5514
5515 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5516
5517 Boolean
5518 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5519 {
5520         int startPV, multi, lineStart, origIndex = index;
5521         char *p, buf2[MSG_SIZ];
5522         ChessProgramState *cps = (pane ? &second : &first);
5523
5524         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5525         lastX = x; lastY = y;
5526         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5527         lineStart = startPV = index;
5528         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5529         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5530         index = startPV;
5531         do{ while(buf[index] && buf[index] != '\n') index++;
5532         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5533         buf[index] = 0;
5534         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5535                 int n = cps->option[multi].value;
5536                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5537                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5538                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5539                 cps->option[multi].value = n;
5540                 *start = *end = 0;
5541                 return FALSE;
5542         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5543                 ExcludeClick(origIndex - lineStart);
5544                 return FALSE;
5545         }
5546         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5547         *start = startPV; *end = index-1;
5548         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5549         return TRUE;
5550 }
5551
5552 char *
5553 PvToSAN (char *pv)
5554 {
5555         static char buf[10*MSG_SIZ];
5556         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5557         *buf = NULLCHAR;
5558         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5559         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5560         for(i = forwardMostMove; i<endPV; i++){
5561             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5562             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5563             k += strlen(buf+k);
5564         }
5565         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5566         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5567         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5568         endPV = savedEnd;
5569         return buf;
5570 }
5571
5572 Boolean
5573 LoadPV (int x, int y)
5574 { // called on right mouse click to load PV
5575   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5576   lastX = x; lastY = y;
5577   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5578   extendGame = FALSE;
5579   return TRUE;
5580 }
5581
5582 void
5583 UnLoadPV ()
5584 {
5585   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5586   if(endPV < 0) return;
5587   if(appData.autoCopyPV) CopyFENToClipboard();
5588   endPV = -1;
5589   if(extendGame && currentMove > forwardMostMove) {
5590         Boolean saveAnimate = appData.animate;
5591         if(pushed) {
5592             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5593                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5594             } else storedGames--; // abandon shelved tail of original game
5595         }
5596         pushed = FALSE;
5597         forwardMostMove = currentMove;
5598         currentMove = oldFMM;
5599         appData.animate = FALSE;
5600         ToNrEvent(forwardMostMove);
5601         appData.animate = saveAnimate;
5602   }
5603   currentMove = forwardMostMove;
5604   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5605   ClearPremoveHighlights();
5606   DrawPosition(TRUE, boards[currentMove]);
5607 }
5608
5609 void
5610 MovePV (int x, int y, int h)
5611 { // step through PV based on mouse coordinates (called on mouse move)
5612   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5613
5614   // we must somehow check if right button is still down (might be released off board!)
5615   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5616   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5617   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5618   if(!step) return;
5619   lastX = x; lastY = y;
5620
5621   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5622   if(endPV < 0) return;
5623   if(y < margin) step = 1; else
5624   if(y > h - margin) step = -1;
5625   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5626   currentMove += step;
5627   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5628   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5629                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5630   DrawPosition(FALSE, boards[currentMove]);
5631 }
5632
5633
5634 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5635 // All positions will have equal probability, but the current method will not provide a unique
5636 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5637 #define DARK 1
5638 #define LITE 2
5639 #define ANY 3
5640
5641 int squaresLeft[4];
5642 int piecesLeft[(int)BlackPawn];
5643 int seed, nrOfShuffles;
5644
5645 void
5646 GetPositionNumber ()
5647 {       // sets global variable seed
5648         int i;
5649
5650         seed = appData.defaultFrcPosition;
5651         if(seed < 0) { // randomize based on time for negative FRC position numbers
5652                 for(i=0; i<50; i++) seed += random();
5653                 seed = random() ^ random() >> 8 ^ random() << 8;
5654                 if(seed<0) seed = -seed;
5655         }
5656 }
5657
5658 int
5659 put (Board board, int pieceType, int rank, int n, int shade)
5660 // put the piece on the (n-1)-th empty squares of the given shade
5661 {
5662         int i;
5663
5664         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5665                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5666                         board[rank][i] = (ChessSquare) pieceType;
5667                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5668                         squaresLeft[ANY]--;
5669                         piecesLeft[pieceType]--;
5670                         return i;
5671                 }
5672         }
5673         return -1;
5674 }
5675
5676
5677 void
5678 AddOnePiece (Board board, int pieceType, int rank, int shade)
5679 // calculate where the next piece goes, (any empty square), and put it there
5680 {
5681         int i;
5682
5683         i = seed % squaresLeft[shade];
5684         nrOfShuffles *= squaresLeft[shade];
5685         seed /= squaresLeft[shade];
5686         put(board, pieceType, rank, i, shade);
5687 }
5688
5689 void
5690 AddTwoPieces (Board board, int pieceType, int rank)
5691 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5692 {
5693         int i, n=squaresLeft[ANY], j=n-1, k;
5694
5695         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5696         i = seed % k;  // pick one
5697         nrOfShuffles *= k;
5698         seed /= k;
5699         while(i >= j) i -= j--;
5700         j = n - 1 - j; i += j;
5701         put(board, pieceType, rank, j, ANY);
5702         put(board, pieceType, rank, i, ANY);
5703 }
5704
5705 void
5706 SetUpShuffle (Board board, int number)
5707 {
5708         int i, p, first=1;
5709
5710         GetPositionNumber(); nrOfShuffles = 1;
5711
5712         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5713         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5714         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5715
5716         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5717
5718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5719             p = (int) board[0][i];
5720             if(p < (int) BlackPawn) piecesLeft[p] ++;
5721             board[0][i] = EmptySquare;
5722         }
5723
5724         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5725             // shuffles restricted to allow normal castling put KRR first
5726             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5727                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5728             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5729                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5730             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5731                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5732             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5733                 put(board, WhiteRook, 0, 0, ANY);
5734             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5735         }
5736
5737         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5738             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5739             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5740                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5741                 while(piecesLeft[p] >= 2) {
5742                     AddOnePiece(board, p, 0, LITE);
5743                     AddOnePiece(board, p, 0, DARK);
5744                 }
5745                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5746             }
5747
5748         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5749             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5750             // but we leave King and Rooks for last, to possibly obey FRC restriction
5751             if(p == (int)WhiteRook) continue;
5752             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5753             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5754         }
5755
5756         // now everything is placed, except perhaps King (Unicorn) and Rooks
5757
5758         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5759             // Last King gets castling rights
5760             while(piecesLeft[(int)WhiteUnicorn]) {
5761                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5763             }
5764
5765             while(piecesLeft[(int)WhiteKing]) {
5766                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5767                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5768             }
5769
5770
5771         } else {
5772             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5773             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5774         }
5775
5776         // Only Rooks can be left; simply place them all
5777         while(piecesLeft[(int)WhiteRook]) {
5778                 i = put(board, WhiteRook, 0, 0, ANY);
5779                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5780                         if(first) {
5781                                 first=0;
5782                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5783                         }
5784                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5785                 }
5786         }
5787         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5788             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5789         }
5790
5791         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5792 }
5793
5794 int
5795 SetCharTable (char *table, const char * map)
5796 /* [HGM] moved here from winboard.c because of its general usefulness */
5797 /*       Basically a safe strcpy that uses the last character as King */
5798 {
5799     int result = FALSE; int NrPieces;
5800
5801     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5802                     && NrPieces >= 12 && !(NrPieces&1)) {
5803         int i; /* [HGM] Accept even length from 12 to 34 */
5804
5805         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5806         for( i=0; i<NrPieces/2-1; i++ ) {
5807             table[i] = map[i];
5808             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5809         }
5810         table[(int) WhiteKing]  = map[NrPieces/2-1];
5811         table[(int) BlackKing]  = map[NrPieces-1];
5812
5813         result = TRUE;
5814     }
5815
5816     return result;
5817 }
5818
5819 void
5820 Prelude (Board board)
5821 {       // [HGM] superchess: random selection of exo-pieces
5822         int i, j, k; ChessSquare p;
5823         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5824
5825         GetPositionNumber(); // use FRC position number
5826
5827         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5828             SetCharTable(pieceToChar, appData.pieceToCharTable);
5829             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5830                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5831         }
5832
5833         j = seed%4;                 seed /= 4;
5834         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5835         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5836         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5837         j = seed%3 + (seed%3 >= j); seed /= 3;
5838         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5839         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5840         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5841         j = seed%3;                 seed /= 3;
5842         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5843         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5844         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5845         j = seed%2 + (seed%2 >= j); seed /= 2;
5846         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5847         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5848         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5849         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5850         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5851         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5852         put(board, exoPieces[0],    0, 0, ANY);
5853         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5854 }
5855
5856 void
5857 InitPosition (int redraw)
5858 {
5859     ChessSquare (* pieces)[BOARD_FILES];
5860     int i, j, pawnRow, overrule,
5861     oldx = gameInfo.boardWidth,
5862     oldy = gameInfo.boardHeight,
5863     oldh = gameInfo.holdingsWidth;
5864     static int oldv;
5865
5866     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5867
5868     /* [AS] Initialize pv info list [HGM] and game status */
5869     {
5870         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5871             pvInfoList[i].depth = 0;
5872             boards[i][EP_STATUS] = EP_NONE;
5873             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5874         }
5875
5876         initialRulePlies = 0; /* 50-move counter start */
5877
5878         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5879         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5880     }
5881
5882
5883     /* [HGM] logic here is completely changed. In stead of full positions */
5884     /* the initialized data only consist of the two backranks. The switch */
5885     /* selects which one we will use, which is than copied to the Board   */
5886     /* initialPosition, which for the rest is initialized by Pawns and    */
5887     /* empty squares. This initial position is then copied to boards[0],  */
5888     /* possibly after shuffling, so that it remains available.            */
5889
5890     gameInfo.holdingsWidth = 0; /* default board sizes */
5891     gameInfo.boardWidth    = 8;
5892     gameInfo.boardHeight   = 8;
5893     gameInfo.holdingsSize  = 0;
5894     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5895     for(i=0; i<BOARD_FILES-2; i++)
5896       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5897     initialPosition[EP_STATUS] = EP_NONE;
5898     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5899     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5900          SetCharTable(pieceNickName, appData.pieceNickNames);
5901     else SetCharTable(pieceNickName, "............");
5902     pieces = FIDEArray;
5903
5904     switch (gameInfo.variant) {
5905     case VariantFischeRandom:
5906       shuffleOpenings = TRUE;
5907     default:
5908       break;
5909     case VariantShatranj:
5910       pieces = ShatranjArray;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5913       break;
5914     case VariantMakruk:
5915       pieces = makrukArray;
5916       nrCastlingRights = 0;
5917       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5918       break;
5919     case VariantASEAN:
5920       pieces = aseanArray;
5921       nrCastlingRights = 0;
5922       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5923       break;
5924     case VariantTwoKings:
5925       pieces = twoKingsArray;
5926       break;
5927     case VariantGrand:
5928       pieces = GrandArray;
5929       nrCastlingRights = 0;
5930       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5931       gameInfo.boardWidth = 10;
5932       gameInfo.boardHeight = 10;
5933       gameInfo.holdingsSize = 7;
5934       break;
5935     case VariantCapaRandom:
5936       shuffleOpenings = TRUE;
5937     case VariantCapablanca:
5938       pieces = CapablancaArray;
5939       gameInfo.boardWidth = 10;
5940       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5941       break;
5942     case VariantGothic:
5943       pieces = GothicArray;
5944       gameInfo.boardWidth = 10;
5945       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5946       break;
5947     case VariantSChess:
5948       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5949       gameInfo.holdingsSize = 7;
5950       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5951       break;
5952     case VariantJanus:
5953       pieces = JanusArray;
5954       gameInfo.boardWidth = 10;
5955       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5956       nrCastlingRights = 6;
5957         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5958         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5959         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5960         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5961         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5962         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5963       break;
5964     case VariantFalcon:
5965       pieces = FalconArray;
5966       gameInfo.boardWidth = 10;
5967       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5968       break;
5969     case VariantXiangqi:
5970       pieces = XiangqiArray;
5971       gameInfo.boardWidth  = 9;
5972       gameInfo.boardHeight = 10;
5973       nrCastlingRights = 0;
5974       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5975       break;
5976     case VariantShogi:
5977       pieces = ShogiArray;
5978       gameInfo.boardWidth  = 9;
5979       gameInfo.boardHeight = 9;
5980       gameInfo.holdingsSize = 7;
5981       nrCastlingRights = 0;
5982       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5983       break;
5984     case VariantCourier:
5985       pieces = CourierArray;
5986       gameInfo.boardWidth  = 12;
5987       nrCastlingRights = 0;
5988       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5989       break;
5990     case VariantKnightmate:
5991       pieces = KnightmateArray;
5992       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5993       break;
5994     case VariantSpartan:
5995       pieces = SpartanArray;
5996       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5997       break;
5998     case VariantFairy:
5999       pieces = fairyArray;
6000       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6001       break;
6002     case VariantGreat:
6003       pieces = GreatArray;
6004       gameInfo.boardWidth = 10;
6005       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6006       gameInfo.holdingsSize = 8;
6007       break;
6008     case VariantSuper:
6009       pieces = FIDEArray;
6010       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6011       gameInfo.holdingsSize = 8;
6012       startedFromSetupPosition = TRUE;
6013       break;
6014     case VariantCrazyhouse:
6015     case VariantBughouse:
6016       pieces = FIDEArray;
6017       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6018       gameInfo.holdingsSize = 5;
6019       break;
6020     case VariantWildCastle:
6021       pieces = FIDEArray;
6022       /* !!?shuffle with kings guaranteed to be on d or e file */
6023       shuffleOpenings = 1;
6024       break;
6025     case VariantNoCastle:
6026       pieces = FIDEArray;
6027       nrCastlingRights = 0;
6028       /* !!?unconstrained back-rank shuffle */
6029       shuffleOpenings = 1;
6030       break;
6031     }
6032
6033     overrule = 0;
6034     if(appData.NrFiles >= 0) {
6035         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6036         gameInfo.boardWidth = appData.NrFiles;
6037     }
6038     if(appData.NrRanks >= 0) {
6039         gameInfo.boardHeight = appData.NrRanks;
6040     }
6041     if(appData.holdingsSize >= 0) {
6042         i = appData.holdingsSize;
6043         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6044         gameInfo.holdingsSize = i;
6045     }
6046     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6047     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6048         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6049
6050     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6051     if(pawnRow < 1) pawnRow = 1;
6052     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6053
6054     /* User pieceToChar list overrules defaults */
6055     if(appData.pieceToCharTable != NULL)
6056         SetCharTable(pieceToChar, appData.pieceToCharTable);
6057
6058     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6059
6060         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6061             s = (ChessSquare) 0; /* account holding counts in guard band */
6062         for( i=0; i<BOARD_HEIGHT; i++ )
6063             initialPosition[i][j] = s;
6064
6065         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6066         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6067         initialPosition[pawnRow][j] = WhitePawn;
6068         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6069         if(gameInfo.variant == VariantXiangqi) {
6070             if(j&1) {
6071                 initialPosition[pawnRow][j] =
6072                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6073                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6074                    initialPosition[2][j] = WhiteCannon;
6075                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6076                 }
6077             }
6078         }
6079         if(gameInfo.variant == VariantGrand) {
6080             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6081                initialPosition[0][j] = WhiteRook;
6082                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6083             }
6084         }
6085         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6086     }
6087     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6088
6089             j=BOARD_LEFT+1;
6090             initialPosition[1][j] = WhiteBishop;
6091             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6092             j=BOARD_RGHT-2;
6093             initialPosition[1][j] = WhiteRook;
6094             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6095     }
6096
6097     if( nrCastlingRights == -1) {
6098         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6099         /*       This sets default castling rights from none to normal corners   */
6100         /* Variants with other castling rights must set them themselves above    */
6101         nrCastlingRights = 6;
6102
6103         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6104         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6105         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6106         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6107         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6108         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6109      }
6110
6111      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6112      if(gameInfo.variant == VariantGreat) { // promotion commoners
6113         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6114         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6115         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6116         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6117      }
6118      if( gameInfo.variant == VariantSChess ) {
6119       initialPosition[1][0] = BlackMarshall;
6120       initialPosition[2][0] = BlackAngel;
6121       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6122       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6123       initialPosition[1][1] = initialPosition[2][1] =
6124       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6125      }
6126   if (appData.debugMode) {
6127     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6128   }
6129     if(shuffleOpenings) {
6130         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6131         startedFromSetupPosition = TRUE;
6132     }
6133     if(startedFromPositionFile) {
6134       /* [HGM] loadPos: use PositionFile for every new game */
6135       CopyBoard(initialPosition, filePosition);
6136       for(i=0; i<nrCastlingRights; i++)
6137           initialRights[i] = filePosition[CASTLING][i];
6138       startedFromSetupPosition = TRUE;
6139     }
6140
6141     CopyBoard(boards[0], initialPosition);
6142
6143     if(oldx != gameInfo.boardWidth ||
6144        oldy != gameInfo.boardHeight ||
6145        oldv != gameInfo.variant ||
6146        oldh != gameInfo.holdingsWidth
6147                                          )
6148             InitDrawingSizes(-2 ,0);
6149
6150     oldv = gameInfo.variant;
6151     if (redraw)
6152       DrawPosition(TRUE, boards[currentMove]);
6153 }
6154
6155 void
6156 SendBoard (ChessProgramState *cps, int moveNum)
6157 {
6158     char message[MSG_SIZ];
6159
6160     if (cps->useSetboard) {
6161       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6162       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6163       SendToProgram(message, cps);
6164       free(fen);
6165
6166     } else {
6167       ChessSquare *bp;
6168       int i, j, left=0, right=BOARD_WIDTH;
6169       /* Kludge to set black to move, avoiding the troublesome and now
6170        * deprecated "black" command.
6171        */
6172       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6173         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6174
6175       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6176
6177       SendToProgram("edit\n", cps);
6178       SendToProgram("#\n", cps);
6179       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6180         bp = &boards[moveNum][i][left];
6181         for (j = left; j < right; j++, bp++) {
6182           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6183           if ((int) *bp < (int) BlackPawn) {
6184             if(j == BOARD_RGHT+1)
6185                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6186             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6187             if(message[0] == '+' || message[0] == '~') {
6188               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6189                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6190                         AAA + j, ONE + i);
6191             }
6192             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6193                 message[1] = BOARD_RGHT   - 1 - j + '1';
6194                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6195             }
6196             SendToProgram(message, cps);
6197           }
6198         }
6199       }
6200
6201       SendToProgram("c\n", cps);
6202       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6203         bp = &boards[moveNum][i][left];
6204         for (j = left; j < right; j++, bp++) {
6205           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6206           if (((int) *bp != (int) EmptySquare)
6207               && ((int) *bp >= (int) BlackPawn)) {
6208             if(j == BOARD_LEFT-2)
6209                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6210             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6211                     AAA + j, ONE + i);
6212             if(message[0] == '+' || message[0] == '~') {
6213               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6214                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6215                         AAA + j, ONE + i);
6216             }
6217             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6218                 message[1] = BOARD_RGHT   - 1 - j + '1';
6219                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6220             }
6221             SendToProgram(message, cps);
6222           }
6223         }
6224       }
6225
6226       SendToProgram(".\n", cps);
6227     }
6228     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6229 }
6230
6231 char exclusionHeader[MSG_SIZ];
6232 int exCnt, excludePtr;
6233 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6234 static Exclusion excluTab[200];
6235 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6236
6237 static void
6238 WriteMap (int s)
6239 {
6240     int j;
6241     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6242     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6243 }
6244
6245 static void
6246 ClearMap ()
6247 {
6248     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6249     excludePtr = 24; exCnt = 0;
6250     WriteMap(0);
6251 }
6252
6253 static void
6254 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6255 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6256     char buf[2*MOVE_LEN], *p;
6257     Exclusion *e = excluTab;
6258     int i;
6259     for(i=0; i<exCnt; i++)
6260         if(e[i].ff == fromX && e[i].fr == fromY &&
6261            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6262     if(i == exCnt) { // was not in exclude list; add it
6263         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6264         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6265             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6266             return; // abort
6267         }
6268         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6269         excludePtr++; e[i].mark = excludePtr++;
6270         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6271         exCnt++;
6272     }
6273     exclusionHeader[e[i].mark] = state;
6274 }
6275
6276 static int
6277 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6278 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6279     char buf[MSG_SIZ];
6280     int j, k;
6281     ChessMove moveType;
6282     if((signed char)promoChar == -1) { // kludge to indicate best move
6283         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6284             return 1; // if unparsable, abort
6285     }
6286     // update exclusion map (resolving toggle by consulting existing state)
6287     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6288     j = k%8; k >>= 3;
6289     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6290     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6291          excludeMap[k] |=   1<<j;
6292     else excludeMap[k] &= ~(1<<j);
6293     // update header
6294     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6295     // inform engine
6296     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6297     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6298     SendToBoth(buf);
6299     return (state == '+');
6300 }
6301
6302 static void
6303 ExcludeClick (int index)
6304 {
6305     int i, j;
6306     Exclusion *e = excluTab;
6307     if(index < 25) { // none, best or tail clicked
6308         if(index < 13) { // none: include all
6309             WriteMap(0); // clear map
6310             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6311             SendToBoth("include all\n"); // and inform engine
6312         } else if(index > 18) { // tail
6313             if(exclusionHeader[19] == '-') { // tail was excluded
6314                 SendToBoth("include all\n");
6315                 WriteMap(0); // clear map completely
6316                 // now re-exclude selected moves
6317                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6318                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6319             } else { // tail was included or in mixed state
6320                 SendToBoth("exclude all\n");
6321                 WriteMap(0xFF); // fill map completely
6322                 // now re-include selected moves
6323                 j = 0; // count them
6324                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6325                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6326                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6327             }
6328         } else { // best
6329             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6330         }
6331     } else {
6332         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6333             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6334             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6335             break;
6336         }
6337     }
6338 }
6339
6340 ChessSquare
6341 DefaultPromoChoice (int white)
6342 {
6343     ChessSquare result;
6344     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6345        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6346         result = WhiteFerz; // no choice
6347     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6348         result= WhiteKing; // in Suicide Q is the last thing we want
6349     else if(gameInfo.variant == VariantSpartan)
6350         result = white ? WhiteQueen : WhiteAngel;
6351     else result = WhiteQueen;
6352     if(!white) result = WHITE_TO_BLACK result;
6353     return result;
6354 }
6355
6356 static int autoQueen; // [HGM] oneclick
6357
6358 int
6359 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6360 {
6361     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6362     /* [HGM] add Shogi promotions */
6363     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6364     ChessSquare piece;
6365     ChessMove moveType;
6366     Boolean premove;
6367
6368     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6369     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6370
6371     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6372       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6373         return FALSE;
6374
6375     piece = boards[currentMove][fromY][fromX];
6376     if(gameInfo.variant == VariantShogi) {
6377         promotionZoneSize = BOARD_HEIGHT/3;
6378         highestPromotingPiece = (int)WhiteFerz;
6379     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6380         promotionZoneSize = 3;
6381     }
6382
6383     // Treat Lance as Pawn when it is not representing Amazon
6384     if(gameInfo.variant != VariantSuper) {
6385         if(piece == WhiteLance) piece = WhitePawn; else
6386         if(piece == BlackLance) piece = BlackPawn;
6387     }
6388
6389     // next weed out all moves that do not touch the promotion zone at all
6390     if((int)piece >= BlackPawn) {
6391         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6392              return FALSE;
6393         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6394     } else {
6395         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6396            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6397     }
6398
6399     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6400
6401     // weed out mandatory Shogi promotions
6402     if(gameInfo.variant == VariantShogi) {
6403         if(piece >= BlackPawn) {
6404             if(toY == 0 && piece == BlackPawn ||
6405                toY == 0 && piece == BlackQueen ||
6406                toY <= 1 && piece == BlackKnight) {
6407                 *promoChoice = '+';
6408                 return FALSE;
6409             }
6410         } else {
6411             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6412                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6413                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6414                 *promoChoice = '+';
6415                 return FALSE;
6416             }
6417         }
6418     }
6419
6420     // weed out obviously illegal Pawn moves
6421     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6422         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6423         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6424         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6425         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6426         // note we are not allowed to test for valid (non-)capture, due to premove
6427     }
6428
6429     // we either have a choice what to promote to, or (in Shogi) whether to promote
6430     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6431        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6432         *promoChoice = PieceToChar(BlackFerz);  // no choice
6433         return FALSE;
6434     }
6435     // no sense asking what we must promote to if it is going to explode...
6436     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6437         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6438         return FALSE;
6439     }
6440     // give caller the default choice even if we will not make it
6441     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6442     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6443     if(        sweepSelect && gameInfo.variant != VariantGreat
6444                            && gameInfo.variant != VariantGrand
6445                            && gameInfo.variant != VariantSuper) return FALSE;
6446     if(autoQueen) return FALSE; // predetermined
6447
6448     // suppress promotion popup on illegal moves that are not premoves
6449     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6450               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6451     if(appData.testLegality && !premove) {
6452         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6453                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6454         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6455             return FALSE;
6456     }
6457
6458     return TRUE;
6459 }
6460
6461 int
6462 InPalace (int row, int column)
6463 {   /* [HGM] for Xiangqi */
6464     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6465          column < (BOARD_WIDTH + 4)/2 &&
6466          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6467     return FALSE;
6468 }
6469
6470 int
6471 PieceForSquare (int x, int y)
6472 {
6473   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6474      return -1;
6475   else
6476      return boards[currentMove][y][x];
6477 }
6478
6479 int
6480 OKToStartUserMove (int x, int y)
6481 {
6482     ChessSquare from_piece;
6483     int white_piece;
6484
6485     if (matchMode) return FALSE;
6486     if (gameMode == EditPosition) return TRUE;
6487
6488     if (x >= 0 && y >= 0)
6489       from_piece = boards[currentMove][y][x];
6490     else
6491       from_piece = EmptySquare;
6492
6493     if (from_piece == EmptySquare) return FALSE;
6494
6495     white_piece = (int)from_piece >= (int)WhitePawn &&
6496       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6497
6498     switch (gameMode) {
6499       case AnalyzeFile:
6500       case TwoMachinesPlay:
6501       case EndOfGame:
6502         return FALSE;
6503
6504       case IcsObserving:
6505       case IcsIdle:
6506         return FALSE;
6507
6508       case MachinePlaysWhite:
6509       case IcsPlayingBlack:
6510         if (appData.zippyPlay) return FALSE;
6511         if (white_piece) {
6512             DisplayMoveError(_("You are playing Black"));
6513             return FALSE;
6514         }
6515         break;
6516
6517       case MachinePlaysBlack:
6518       case IcsPlayingWhite:
6519         if (appData.zippyPlay) return FALSE;
6520         if (!white_piece) {
6521             DisplayMoveError(_("You are playing White"));
6522             return FALSE;
6523         }
6524         break;
6525
6526       case PlayFromGameFile:
6527             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6528       case EditGame:
6529         if (!white_piece && WhiteOnMove(currentMove)) {
6530             DisplayMoveError(_("It is White's turn"));
6531             return FALSE;
6532         }
6533         if (white_piece && !WhiteOnMove(currentMove)) {
6534             DisplayMoveError(_("It is Black's turn"));
6535             return FALSE;
6536         }
6537         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6538             /* Editing correspondence game history */
6539             /* Could disallow this or prompt for confirmation */
6540             cmailOldMove = -1;
6541         }
6542         break;
6543
6544       case BeginningOfGame:
6545         if (appData.icsActive) return FALSE;
6546         if (!appData.noChessProgram) {
6547             if (!white_piece) {
6548                 DisplayMoveError(_("You are playing White"));
6549                 return FALSE;
6550             }
6551         }
6552         break;
6553
6554       case Training:
6555         if (!white_piece && WhiteOnMove(currentMove)) {
6556             DisplayMoveError(_("It is White's turn"));
6557             return FALSE;
6558         }
6559         if (white_piece && !WhiteOnMove(currentMove)) {
6560             DisplayMoveError(_("It is Black's turn"));
6561             return FALSE;
6562         }
6563         break;
6564
6565       default:
6566       case IcsExamining:
6567         break;
6568     }
6569     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6570         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6571         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6572         && gameMode != AnalyzeFile && gameMode != Training) {
6573         DisplayMoveError(_("Displayed position is not current"));
6574         return FALSE;
6575     }
6576     return TRUE;
6577 }
6578
6579 Boolean
6580 OnlyMove (int *x, int *y, Boolean captures)
6581 {
6582     DisambiguateClosure cl;
6583     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6584     switch(gameMode) {
6585       case MachinePlaysBlack:
6586       case IcsPlayingWhite:
6587       case BeginningOfGame:
6588         if(!WhiteOnMove(currentMove)) return FALSE;
6589         break;
6590       case MachinePlaysWhite:
6591       case IcsPlayingBlack:
6592         if(WhiteOnMove(currentMove)) return FALSE;
6593         break;
6594       case EditGame:
6595         break;
6596       default:
6597         return FALSE;
6598     }
6599     cl.pieceIn = EmptySquare;
6600     cl.rfIn = *y;
6601     cl.ffIn = *x;
6602     cl.rtIn = -1;
6603     cl.ftIn = -1;
6604     cl.promoCharIn = NULLCHAR;
6605     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6606     if( cl.kind == NormalMove ||
6607         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6608         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6609         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6610       fromX = cl.ff;
6611       fromY = cl.rf;
6612       *x = cl.ft;
6613       *y = cl.rt;
6614       return TRUE;
6615     }
6616     if(cl.kind != ImpossibleMove) return FALSE;
6617     cl.pieceIn = EmptySquare;
6618     cl.rfIn = -1;
6619     cl.ffIn = -1;
6620     cl.rtIn = *y;
6621     cl.ftIn = *x;
6622     cl.promoCharIn = NULLCHAR;
6623     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6624     if( cl.kind == NormalMove ||
6625         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6626         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6627         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6628       fromX = cl.ff;
6629       fromY = cl.rf;
6630       *x = cl.ft;
6631       *y = cl.rt;
6632       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6633       return TRUE;
6634     }
6635     return FALSE;
6636 }
6637
6638 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6639 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6640 int lastLoadGameUseList = FALSE;
6641 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6642 ChessMove lastLoadGameStart = EndOfFile;
6643 int doubleClick;
6644
6645 void
6646 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6647 {
6648     ChessMove moveType;
6649     ChessSquare pup;
6650     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6651
6652     /* Check if the user is playing in turn.  This is complicated because we
6653        let the user "pick up" a piece before it is his turn.  So the piece he
6654        tried to pick up may have been captured by the time he puts it down!
6655        Therefore we use the color the user is supposed to be playing in this
6656        test, not the color of the piece that is currently on the starting
6657        square---except in EditGame mode, where the user is playing both
6658        sides; fortunately there the capture race can't happen.  (It can
6659        now happen in IcsExamining mode, but that's just too bad.  The user
6660        will get a somewhat confusing message in that case.)
6661        */
6662
6663     switch (gameMode) {
6664       case AnalyzeFile:
6665       case TwoMachinesPlay:
6666       case EndOfGame:
6667       case IcsObserving:
6668       case IcsIdle:
6669         /* We switched into a game mode where moves are not accepted,
6670            perhaps while the mouse button was down. */
6671         return;
6672
6673       case MachinePlaysWhite:
6674         /* User is moving for Black */
6675         if (WhiteOnMove(currentMove)) {
6676             DisplayMoveError(_("It is White's turn"));
6677             return;
6678         }
6679         break;
6680
6681       case MachinePlaysBlack:
6682         /* User is moving for White */
6683         if (!WhiteOnMove(currentMove)) {
6684             DisplayMoveError(_("It is Black's turn"));
6685             return;
6686         }
6687         break;
6688
6689       case PlayFromGameFile:
6690             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6691       case EditGame:
6692       case IcsExamining:
6693       case BeginningOfGame:
6694       case AnalyzeMode:
6695       case Training:
6696         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6697         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6698             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6699             /* User is moving for Black */
6700             if (WhiteOnMove(currentMove)) {
6701                 DisplayMoveError(_("It is White's turn"));
6702                 return;
6703             }
6704         } else {
6705             /* User is moving for White */
6706             if (!WhiteOnMove(currentMove)) {
6707                 DisplayMoveError(_("It is Black's turn"));
6708                 return;
6709             }
6710         }
6711         break;
6712
6713       case IcsPlayingBlack:
6714         /* User is moving for Black */
6715         if (WhiteOnMove(currentMove)) {
6716             if (!appData.premove) {
6717                 DisplayMoveError(_("It is White's turn"));
6718             } else if (toX >= 0 && toY >= 0) {
6719                 premoveToX = toX;
6720                 premoveToY = toY;
6721                 premoveFromX = fromX;
6722                 premoveFromY = fromY;
6723                 premovePromoChar = promoChar;
6724                 gotPremove = 1;
6725                 if (appData.debugMode)
6726                     fprintf(debugFP, "Got premove: fromX %d,"
6727                             "fromY %d, toX %d, toY %d\n",
6728                             fromX, fromY, toX, toY);
6729             }
6730             return;
6731         }
6732         break;
6733
6734       case IcsPlayingWhite:
6735         /* User is moving for White */
6736         if (!WhiteOnMove(currentMove)) {
6737             if (!appData.premove) {
6738                 DisplayMoveError(_("It is Black's turn"));
6739             } else if (toX >= 0 && toY >= 0) {
6740                 premoveToX = toX;
6741                 premoveToY = toY;
6742                 premoveFromX = fromX;
6743                 premoveFromY = fromY;
6744                 premovePromoChar = promoChar;
6745                 gotPremove = 1;
6746                 if (appData.debugMode)
6747                     fprintf(debugFP, "Got premove: fromX %d,"
6748                             "fromY %d, toX %d, toY %d\n",
6749                             fromX, fromY, toX, toY);
6750             }
6751             return;
6752         }
6753         break;
6754
6755       default:
6756         break;
6757
6758       case EditPosition:
6759         /* EditPosition, empty square, or different color piece;
6760            click-click move is possible */
6761         if (toX == -2 || toY == -2) {
6762             boards[0][fromY][fromX] = EmptySquare;
6763             DrawPosition(FALSE, boards[currentMove]);
6764             return;
6765         } else if (toX >= 0 && toY >= 0) {
6766             boards[0][toY][toX] = boards[0][fromY][fromX];
6767             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6768                 if(boards[0][fromY][0] != EmptySquare) {
6769                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6770                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6771                 }
6772             } else
6773             if(fromX == BOARD_RGHT+1) {
6774                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6775                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6776                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6777                 }
6778             } else
6779             boards[0][fromY][fromX] = gatingPiece;
6780             DrawPosition(FALSE, boards[currentMove]);
6781             return;
6782         }
6783         return;
6784     }
6785
6786     if(toX < 0 || toY < 0) return;
6787     pup = boards[currentMove][toY][toX];
6788
6789     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6790     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6791          if( pup != EmptySquare ) return;
6792          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6793            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6794                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6795            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6796            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6797            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6798            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6799          fromY = DROP_RANK;
6800     }
6801
6802     /* [HGM] always test for legality, to get promotion info */
6803     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6804                                          fromY, fromX, toY, toX, promoChar);
6805
6806     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6807
6808     /* [HGM] but possibly ignore an IllegalMove result */
6809     if (appData.testLegality) {
6810         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6811             DisplayMoveError(_("Illegal move"));
6812             return;
6813         }
6814     }
6815
6816     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6817         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6818              ClearPremoveHighlights(); // was included
6819         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6820         return;
6821     }
6822
6823     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6824 }
6825
6826 /* Common tail of UserMoveEvent and DropMenuEvent */
6827 int
6828 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6829 {
6830     char *bookHit = 0;
6831
6832     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6833         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6834         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6835         if(WhiteOnMove(currentMove)) {
6836             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6837         } else {
6838             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6839         }
6840     }
6841
6842     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6843        move type in caller when we know the move is a legal promotion */
6844     if(moveType == NormalMove && promoChar)
6845         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6846
6847     /* [HGM] <popupFix> The following if has been moved here from
6848        UserMoveEvent(). Because it seemed to belong here (why not allow
6849        piece drops in training games?), and because it can only be
6850        performed after it is known to what we promote. */
6851     if (gameMode == Training) {
6852       /* compare the move played on the board to the next move in the
6853        * game. If they match, display the move and the opponent's response.
6854        * If they don't match, display an error message.
6855        */
6856       int saveAnimate;
6857       Board testBoard;
6858       CopyBoard(testBoard, boards[currentMove]);
6859       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6860
6861       if (CompareBoards(testBoard, boards[currentMove+1])) {
6862         ForwardInner(currentMove+1);
6863
6864         /* Autoplay the opponent's response.
6865          * if appData.animate was TRUE when Training mode was entered,
6866          * the response will be animated.
6867          */
6868         saveAnimate = appData.animate;
6869         appData.animate = animateTraining;
6870         ForwardInner(currentMove+1);
6871         appData.animate = saveAnimate;
6872
6873         /* check for the end of the game */
6874         if (currentMove >= forwardMostMove) {
6875           gameMode = PlayFromGameFile;
6876           ModeHighlight();
6877           SetTrainingModeOff();
6878           DisplayInformation(_("End of game"));
6879         }
6880       } else {
6881         DisplayError(_("Incorrect move"), 0);
6882       }
6883       return 1;
6884     }
6885
6886   /* Ok, now we know that the move is good, so we can kill
6887      the previous line in Analysis Mode */
6888   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6889                                 && currentMove < forwardMostMove) {
6890     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6891     else forwardMostMove = currentMove;
6892   }
6893
6894   ClearMap();
6895
6896   /* If we need the chess program but it's dead, restart it */
6897   ResurrectChessProgram();
6898
6899   /* A user move restarts a paused game*/
6900   if (pausing)
6901     PauseEvent();
6902
6903   thinkOutput[0] = NULLCHAR;
6904
6905   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6906
6907   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6908     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6909     return 1;
6910   }
6911
6912   if (gameMode == BeginningOfGame) {
6913     if (appData.noChessProgram) {
6914       gameMode = EditGame;
6915       SetGameInfo();
6916     } else {
6917       char buf[MSG_SIZ];
6918       gameMode = MachinePlaysBlack;
6919       StartClocks();
6920       SetGameInfo();
6921       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6922       DisplayTitle(buf);
6923       if (first.sendName) {
6924         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6925         SendToProgram(buf, &first);
6926       }
6927       StartClocks();
6928     }
6929     ModeHighlight();
6930   }
6931
6932   /* Relay move to ICS or chess engine */
6933   if (appData.icsActive) {
6934     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6935         gameMode == IcsExamining) {
6936       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6937         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6938         SendToICS("draw ");
6939         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6940       }
6941       // also send plain move, in case ICS does not understand atomic claims
6942       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6943       ics_user_moved = 1;
6944     }
6945   } else {
6946     if (first.sendTime && (gameMode == BeginningOfGame ||
6947                            gameMode == MachinePlaysWhite ||
6948                            gameMode == MachinePlaysBlack)) {
6949       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6950     }
6951     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6952          // [HGM] book: if program might be playing, let it use book
6953         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6954         first.maybeThinking = TRUE;
6955     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6956         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6957         SendBoard(&first, currentMove+1);
6958         if(second.analyzing) {
6959             if(!second.useSetboard) SendToProgram("undo\n", &second);
6960             SendBoard(&second, currentMove+1);
6961         }
6962     } else {
6963         SendMoveToProgram(forwardMostMove-1, &first);
6964         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6965     }
6966     if (currentMove == cmailOldMove + 1) {
6967       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6968     }
6969   }
6970
6971   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6972
6973   switch (gameMode) {
6974   case EditGame:
6975     if(appData.testLegality)
6976     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6977     case MT_NONE:
6978     case MT_CHECK:
6979       break;
6980     case MT_CHECKMATE:
6981     case MT_STAINMATE:
6982       if (WhiteOnMove(currentMove)) {
6983         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6984       } else {
6985         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6986       }
6987       break;
6988     case MT_STALEMATE:
6989       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6990       break;
6991     }
6992     break;
6993
6994   case MachinePlaysBlack:
6995   case MachinePlaysWhite:
6996     /* disable certain menu options while machine is thinking */
6997     SetMachineThinkingEnables();
6998     break;
6999
7000   default:
7001     break;
7002   }
7003
7004   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7005   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7006
7007   if(bookHit) { // [HGM] book: simulate book reply
7008         static char bookMove[MSG_SIZ]; // a bit generous?
7009
7010         programStats.nodes = programStats.depth = programStats.time =
7011         programStats.score = programStats.got_only_move = 0;
7012         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7013
7014         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7015         strcat(bookMove, bookHit);
7016         HandleMachineMove(bookMove, &first);
7017   }
7018   return 1;
7019 }
7020
7021 void
7022 MarkByFEN(char *fen)
7023 {
7024         int r, f;
7025         if(!appData.markers || !appData.highlightDragging) return;
7026         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7027         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7028         while(*fen) {
7029             int s = 0;
7030             marker[r][f] = 0;
7031             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7032             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7033             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7034             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7035             if(*fen == 'T') marker[r][f++] = 0; else
7036             if(*fen == 'Y') marker[r][f++] = 1; else
7037             if(*fen == 'G') marker[r][f++] = 3; else
7038             if(*fen == 'B') marker[r][f++] = 4; else
7039             if(*fen == 'C') marker[r][f++] = 5; else
7040             if(*fen == 'M') marker[r][f++] = 6; else
7041             if(*fen == 'W') marker[r][f++] = 7; else
7042             if(*fen == 'D') marker[r][f++] = 8; else
7043             if(*fen == 'R') marker[r][f++] = 2; else {
7044                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7045               f += s; fen -= s>0;
7046             }
7047             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7048             if(r < 0) break;
7049             fen++;
7050         }
7051         DrawPosition(TRUE, NULL);
7052 }
7053
7054 void
7055 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7056 {
7057     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7058     Markers *m = (Markers *) closure;
7059     if(rf == fromY && ff == fromX)
7060         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7061                          || kind == WhiteCapturesEnPassant
7062                          || kind == BlackCapturesEnPassant);
7063     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7064 }
7065
7066 void
7067 MarkTargetSquares (int clear)
7068 {
7069   int x, y, sum=0;
7070   if(clear) { // no reason to ever suppress clearing
7071     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7072     if(!sum) return; // nothing was cleared,no redraw needed
7073   } else {
7074     int capt = 0;
7075     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7076        !appData.testLegality || gameMode == EditPosition) return;
7077     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7078     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7079       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7080       if(capt)
7081       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7082     }
7083   }
7084   DrawPosition(FALSE, NULL);
7085 }
7086
7087 int
7088 Explode (Board board, int fromX, int fromY, int toX, int toY)
7089 {
7090     if(gameInfo.variant == VariantAtomic &&
7091        (board[toY][toX] != EmptySquare ||                     // capture?
7092         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7093                          board[fromY][fromX] == BlackPawn   )
7094       )) {
7095         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7096         return TRUE;
7097     }
7098     return FALSE;
7099 }
7100
7101 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7102
7103 int
7104 CanPromote (ChessSquare piece, int y)
7105 {
7106         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7107         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7108         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7109            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7110            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7111          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7112         return (piece == BlackPawn && y == 1 ||
7113                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7114                 piece == BlackLance && y == 1 ||
7115                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7116 }
7117
7118 void
7119 HoverEvent (int hiX, int hiY, int x, int y)
7120 {
7121         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7122         int r, f;
7123         if(!first.highlight) return;
7124         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7125           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7126             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7127         else if(hiX != x || hiY != y) {
7128           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7129           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7130             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7131           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7132             char buf[MSG_SIZ];
7133             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7134             SendToProgram(buf, &first);
7135           }
7136           SetHighlights(fromX, fromY, x, y);
7137         }
7138 }
7139
7140 void ReportClick(char *action, int x, int y)
7141 {
7142         char buf[MSG_SIZ]; // Inform engine of what user does
7143         int r, f;
7144         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7145           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7146         if(!first.highlight || gameMode == EditPosition) return;
7147         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7148         SendToProgram(buf, &first);
7149 }
7150
7151 void
7152 LeftClick (ClickType clickType, int xPix, int yPix)
7153 {
7154     int x, y;
7155     Boolean saveAnimate;
7156     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7157     char promoChoice = NULLCHAR;
7158     ChessSquare piece;
7159     static TimeMark lastClickTime, prevClickTime;
7160
7161     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7162
7163     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7164
7165     if (clickType == Press) ErrorPopDown();
7166     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7167
7168     x = EventToSquare(xPix, BOARD_WIDTH);
7169     y = EventToSquare(yPix, BOARD_HEIGHT);
7170     if (!flipView && y >= 0) {
7171         y = BOARD_HEIGHT - 1 - y;
7172     }
7173     if (flipView && x >= 0) {
7174         x = BOARD_WIDTH - 1 - x;
7175     }
7176
7177     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7178         defaultPromoChoice = promoSweep;
7179         promoSweep = EmptySquare;   // terminate sweep
7180         promoDefaultAltered = TRUE;
7181         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7182     }
7183
7184     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7185         if(clickType == Release) return; // ignore upclick of click-click destination
7186         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7187         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7188         if(gameInfo.holdingsWidth &&
7189                 (WhiteOnMove(currentMove)
7190                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7191                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7192             // click in right holdings, for determining promotion piece
7193             ChessSquare p = boards[currentMove][y][x];
7194             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7195             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7196             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7197                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7198                 fromX = fromY = -1;
7199                 return;
7200             }
7201         }
7202         DrawPosition(FALSE, boards[currentMove]);
7203         return;
7204     }
7205
7206     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7207     if(clickType == Press
7208             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7209               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7210               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7211         return;
7212
7213     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7214         // could be static click on premove from-square: abort premove
7215         gotPremove = 0;
7216         ClearPremoveHighlights();
7217     }
7218
7219     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7220         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7221
7222     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7223         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7224                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7225         defaultPromoChoice = DefaultPromoChoice(side);
7226     }
7227
7228     autoQueen = appData.alwaysPromoteToQueen;
7229
7230     if (fromX == -1) {
7231       int originalY = y;
7232       gatingPiece = EmptySquare;
7233       if (clickType != Press) {
7234         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7235             DragPieceEnd(xPix, yPix); dragging = 0;
7236             DrawPosition(FALSE, NULL);
7237         }
7238         return;
7239       }
7240       doubleClick = FALSE;
7241       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7242         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7243       }
7244       fromX = x; fromY = y; toX = toY = -1;
7245       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7246          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7247          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7248             /* First square */
7249             if (OKToStartUserMove(fromX, fromY)) {
7250                 second = 0;
7251                 ReportClick("lift", x, y);
7252                 MarkTargetSquares(0);
7253                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7254                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7255                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7256                     promoSweep = defaultPromoChoice;
7257                     selectFlag = 0; lastX = xPix; lastY = yPix;
7258                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7259                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7260                 }
7261                 if (appData.highlightDragging) {
7262                     SetHighlights(fromX, fromY, -1, -1);
7263                 } else {
7264                     ClearHighlights();
7265                 }
7266             } else fromX = fromY = -1;
7267             return;
7268         }
7269     }
7270
7271     /* fromX != -1 */
7272     if (clickType == Press && gameMode != EditPosition) {
7273         ChessSquare fromP;
7274         ChessSquare toP;
7275         int frc;
7276
7277         // ignore off-board to clicks
7278         if(y < 0 || x < 0) return;
7279
7280         /* Check if clicking again on the same color piece */
7281         fromP = boards[currentMove][fromY][fromX];
7282         toP = boards[currentMove][y][x];
7283         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7284         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7285              WhitePawn <= toP && toP <= WhiteKing &&
7286              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7287              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7288             (BlackPawn <= fromP && fromP <= BlackKing &&
7289              BlackPawn <= toP && toP <= BlackKing &&
7290              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7291              !(fromP == BlackKing && toP == BlackRook && frc))) {
7292             /* Clicked again on same color piece -- changed his mind */
7293             second = (x == fromX && y == fromY);
7294             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7295                 second = FALSE; // first double-click rather than scond click
7296                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7297             }
7298             promoDefaultAltered = FALSE;
7299             MarkTargetSquares(1);
7300            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7301             if (appData.highlightDragging) {
7302                 SetHighlights(x, y, -1, -1);
7303             } else {
7304                 ClearHighlights();
7305             }
7306             if (OKToStartUserMove(x, y)) {
7307                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7308                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7309                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7310                  gatingPiece = boards[currentMove][fromY][fromX];
7311                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7312                 fromX = x;
7313                 fromY = y; dragging = 1;
7314                 ReportClick("lift", x, y);
7315                 MarkTargetSquares(0);
7316                 DragPieceBegin(xPix, yPix, FALSE);
7317                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7318                     promoSweep = defaultPromoChoice;
7319                     selectFlag = 0; lastX = xPix; lastY = yPix;
7320                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7321                 }
7322             }
7323            }
7324            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7325            second = FALSE;
7326         }
7327         // ignore clicks on holdings
7328         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7329     }
7330
7331     if (clickType == Release && x == fromX && y == fromY) {
7332         DragPieceEnd(xPix, yPix); dragging = 0;
7333         if(clearFlag) {
7334             // a deferred attempt to click-click move an empty square on top of a piece
7335             boards[currentMove][y][x] = EmptySquare;
7336             ClearHighlights();
7337             DrawPosition(FALSE, boards[currentMove]);
7338             fromX = fromY = -1; clearFlag = 0;
7339             return;
7340         }
7341         if (appData.animateDragging) {
7342             /* Undo animation damage if any */
7343             DrawPosition(FALSE, NULL);
7344         }
7345         if (second || sweepSelecting) {
7346             /* Second up/down in same square; just abort move */
7347             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7348             second = sweepSelecting = 0;
7349             fromX = fromY = -1;
7350             gatingPiece = EmptySquare;
7351             MarkTargetSquares(1);
7352             ClearHighlights();
7353             gotPremove = 0;
7354             ClearPremoveHighlights();
7355         } else {
7356             /* First upclick in same square; start click-click mode */
7357             SetHighlights(x, y, -1, -1);
7358         }
7359         return;
7360     }
7361
7362     clearFlag = 0;
7363
7364     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7365         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7366         DisplayMessage(_("only marked squares are legal"),"");
7367         DrawPosition(TRUE, NULL);
7368         return; // ignore to-click
7369     }
7370
7371     /* we now have a different from- and (possibly off-board) to-square */
7372     /* Completed move */
7373     if(!sweepSelecting) {
7374         toX = x;
7375         toY = y;
7376     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7377
7378     saveAnimate = appData.animate;
7379     if (clickType == Press) {
7380         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7381             // must be Edit Position mode with empty-square selected
7382             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7383             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7384             return;
7385         }
7386         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7387           if(appData.sweepSelect) {
7388             ChessSquare piece = boards[currentMove][fromY][fromX];
7389             promoSweep = defaultPromoChoice;
7390             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7391             selectFlag = 0; lastX = xPix; lastY = yPix;
7392             Sweep(0); // Pawn that is going to promote: preview promotion piece
7393             sweepSelecting = 1;
7394             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7395             MarkTargetSquares(1);
7396           }
7397           return; // promo popup appears on up-click
7398         }
7399         /* Finish clickclick move */
7400         if (appData.animate || appData.highlightLastMove) {
7401             SetHighlights(fromX, fromY, toX, toY);
7402         } else {
7403             ClearHighlights();
7404         }
7405     } else {
7406 #if 0
7407 // [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
7408         /* Finish drag move */
7409         if (appData.highlightLastMove) {
7410             SetHighlights(fromX, fromY, toX, toY);
7411         } else {
7412             ClearHighlights();
7413         }
7414 #endif
7415         DragPieceEnd(xPix, yPix); dragging = 0;
7416         /* Don't animate move and drag both */
7417         appData.animate = FALSE;
7418     }
7419
7420     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7421     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7422         ChessSquare piece = boards[currentMove][fromY][fromX];
7423         if(gameMode == EditPosition && piece != EmptySquare &&
7424            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7425             int n;
7426
7427             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7428                 n = PieceToNumber(piece - (int)BlackPawn);
7429                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7430                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7431                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7432             } else
7433             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7434                 n = PieceToNumber(piece);
7435                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7436                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7437                 boards[currentMove][n][BOARD_WIDTH-2]++;
7438             }
7439             boards[currentMove][fromY][fromX] = EmptySquare;
7440         }
7441         ClearHighlights();
7442         fromX = fromY = -1;
7443         MarkTargetSquares(1);
7444         DrawPosition(TRUE, boards[currentMove]);
7445         return;
7446     }
7447
7448     // off-board moves should not be highlighted
7449     if(x < 0 || y < 0) ClearHighlights();
7450     else ReportClick("put", x, y);
7451
7452     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7453
7454     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7455         SetHighlights(fromX, fromY, toX, toY);
7456         MarkTargetSquares(1);
7457         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7458             // [HGM] super: promotion to captured piece selected from holdings
7459             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7460             promotionChoice = TRUE;
7461             // kludge follows to temporarily execute move on display, without promoting yet
7462             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7463             boards[currentMove][toY][toX] = p;
7464             DrawPosition(FALSE, boards[currentMove]);
7465             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7466             boards[currentMove][toY][toX] = q;
7467             DisplayMessage("Click in holdings to choose piece", "");
7468             return;
7469         }
7470         PromotionPopUp();
7471     } else {
7472         int oldMove = currentMove;
7473         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7474         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7475         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7476         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7477            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7478             DrawPosition(TRUE, boards[currentMove]);
7479         MarkTargetSquares(1);
7480         fromX = fromY = -1;
7481     }
7482     appData.animate = saveAnimate;
7483     if (appData.animate || appData.animateDragging) {
7484         /* Undo animation damage if needed */
7485         DrawPosition(FALSE, NULL);
7486     }
7487 }
7488
7489 int
7490 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7491 {   // front-end-free part taken out of PieceMenuPopup
7492     int whichMenu; int xSqr, ySqr;
7493
7494     if(seekGraphUp) { // [HGM] seekgraph
7495         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7496         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7497         return -2;
7498     }
7499
7500     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7501          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7502         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7503         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7504         if(action == Press)   {
7505             originalFlip = flipView;
7506             flipView = !flipView; // temporarily flip board to see game from partners perspective
7507             DrawPosition(TRUE, partnerBoard);
7508             DisplayMessage(partnerStatus, "");
7509             partnerUp = TRUE;
7510         } else if(action == Release) {
7511             flipView = originalFlip;
7512             DrawPosition(TRUE, boards[currentMove]);
7513             partnerUp = FALSE;
7514         }
7515         return -2;
7516     }
7517
7518     xSqr = EventToSquare(x, BOARD_WIDTH);
7519     ySqr = EventToSquare(y, BOARD_HEIGHT);
7520     if (action == Release) {
7521         if(pieceSweep != EmptySquare) {
7522             EditPositionMenuEvent(pieceSweep, toX, toY);
7523             pieceSweep = EmptySquare;
7524         } else UnLoadPV(); // [HGM] pv
7525     }
7526     if (action != Press) return -2; // return code to be ignored
7527     switch (gameMode) {
7528       case IcsExamining:
7529         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7530       case EditPosition:
7531         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7532         if (xSqr < 0 || ySqr < 0) return -1;
7533         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7534         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7535         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7536         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7537         NextPiece(0);
7538         return 2; // grab
7539       case IcsObserving:
7540         if(!appData.icsEngineAnalyze) return -1;
7541       case IcsPlayingWhite:
7542       case IcsPlayingBlack:
7543         if(!appData.zippyPlay) goto noZip;
7544       case AnalyzeMode:
7545       case AnalyzeFile:
7546       case MachinePlaysWhite:
7547       case MachinePlaysBlack:
7548       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7549         if (!appData.dropMenu) {
7550           LoadPV(x, y);
7551           return 2; // flag front-end to grab mouse events
7552         }
7553         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7554            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7555       case EditGame:
7556       noZip:
7557         if (xSqr < 0 || ySqr < 0) return -1;
7558         if (!appData.dropMenu || appData.testLegality &&
7559             gameInfo.variant != VariantBughouse &&
7560             gameInfo.variant != VariantCrazyhouse) return -1;
7561         whichMenu = 1; // drop menu
7562         break;
7563       default:
7564         return -1;
7565     }
7566
7567     if (((*fromX = xSqr) < 0) ||
7568         ((*fromY = ySqr) < 0)) {
7569         *fromX = *fromY = -1;
7570         return -1;
7571     }
7572     if (flipView)
7573       *fromX = BOARD_WIDTH - 1 - *fromX;
7574     else
7575       *fromY = BOARD_HEIGHT - 1 - *fromY;
7576
7577     return whichMenu;
7578 }
7579
7580 void
7581 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7582 {
7583 //    char * hint = lastHint;
7584     FrontEndProgramStats stats;
7585
7586     stats.which = cps == &first ? 0 : 1;
7587     stats.depth = cpstats->depth;
7588     stats.nodes = cpstats->nodes;
7589     stats.score = cpstats->score;
7590     stats.time = cpstats->time;
7591     stats.pv = cpstats->movelist;
7592     stats.hint = lastHint;
7593     stats.an_move_index = 0;
7594     stats.an_move_count = 0;
7595
7596     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7597         stats.hint = cpstats->move_name;
7598         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7599         stats.an_move_count = cpstats->nr_moves;
7600     }
7601
7602     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
7603
7604     SetProgramStats( &stats );
7605 }
7606
7607 void
7608 ClearEngineOutputPane (int which)
7609 {
7610     static FrontEndProgramStats dummyStats;
7611     dummyStats.which = which;
7612     dummyStats.pv = "#";
7613     SetProgramStats( &dummyStats );
7614 }
7615
7616 #define MAXPLAYERS 500
7617
7618 char *
7619 TourneyStandings (int display)
7620 {
7621     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7622     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7623     char result, *p, *names[MAXPLAYERS];
7624
7625     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7626         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7627     names[0] = p = strdup(appData.participants);
7628     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7629
7630     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7631
7632     while(result = appData.results[nr]) {
7633         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7634         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7635         wScore = bScore = 0;
7636         switch(result) {
7637           case '+': wScore = 2; break;
7638           case '-': bScore = 2; break;
7639           case '=': wScore = bScore = 1; break;
7640           case ' ':
7641           case '*': return strdup("busy"); // tourney not finished
7642         }
7643         score[w] += wScore;
7644         score[b] += bScore;
7645         games[w]++;
7646         games[b]++;
7647         nr++;
7648     }
7649     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7650     for(w=0; w<nPlayers; w++) {
7651         bScore = -1;
7652         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7653         ranking[w] = b; points[w] = bScore; score[b] = -2;
7654     }
7655     p = malloc(nPlayers*34+1);
7656     for(w=0; w<nPlayers && w<display; w++)
7657         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7658     free(names[0]);
7659     return p;
7660 }
7661
7662 void
7663 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7664 {       // count all piece types
7665         int p, f, r;
7666         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7667         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7668         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7669                 p = board[r][f];
7670                 pCnt[p]++;
7671                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7672                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7673                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7674                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7675                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7676                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7677         }
7678 }
7679
7680 int
7681 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7682 {
7683         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7684         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7685
7686         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7687         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7688         if(myPawns == 2 && nMine == 3) // KPP
7689             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7690         if(myPawns == 1 && nMine == 2) // KP
7691             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7692         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7693             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7694         if(myPawns) return FALSE;
7695         if(pCnt[WhiteRook+side])
7696             return pCnt[BlackRook-side] ||
7697                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7698                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7699                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7700         if(pCnt[WhiteCannon+side]) {
7701             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7702             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7703         }
7704         if(pCnt[WhiteKnight+side])
7705             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7706         return FALSE;
7707 }
7708
7709 int
7710 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7711 {
7712         VariantClass v = gameInfo.variant;
7713
7714         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7715         if(v == VariantShatranj) return TRUE; // always winnable through baring
7716         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7717         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7718
7719         if(v == VariantXiangqi) {
7720                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7721
7722                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7723                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7724                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7725                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7726                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7727                 if(stale) // we have at least one last-rank P plus perhaps C
7728                     return majors // KPKX
7729                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7730                 else // KCA*E*
7731                     return pCnt[WhiteFerz+side] // KCAK
7732                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7733                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7734                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7735
7736         } else if(v == VariantKnightmate) {
7737                 if(nMine == 1) return FALSE;
7738                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7739         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7740                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7741
7742                 if(nMine == 1) return FALSE; // bare King
7743                 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
7744                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7745                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7746                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7747                 if(pCnt[WhiteKnight+side])
7748                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7749                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7750                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7751                 if(nBishops)
7752                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7753                 if(pCnt[WhiteAlfil+side])
7754                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7755                 if(pCnt[WhiteWazir+side])
7756                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7757         }
7758
7759         return TRUE;
7760 }
7761
7762 int
7763 CompareWithRights (Board b1, Board b2)
7764 {
7765     int rights = 0;
7766     if(!CompareBoards(b1, b2)) return FALSE;
7767     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7768     /* compare castling rights */
7769     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7770            rights++; /* King lost rights, while rook still had them */
7771     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7772         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7773            rights++; /* but at least one rook lost them */
7774     }
7775     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7776            rights++;
7777     if( b1[CASTLING][5] != NoRights ) {
7778         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7779            rights++;
7780     }
7781     return rights == 0;
7782 }
7783
7784 int
7785 Adjudicate (ChessProgramState *cps)
7786 {       // [HGM] some adjudications useful with buggy engines
7787         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7788         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7789         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7790         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7791         int k, drop, count = 0; static int bare = 1;
7792         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7793         Boolean canAdjudicate = !appData.icsActive;
7794
7795         // most tests only when we understand the game, i.e. legality-checking on
7796             if( appData.testLegality )
7797             {   /* [HGM] Some more adjudications for obstinate engines */
7798                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7799                 static int moveCount = 6;
7800                 ChessMove result;
7801                 char *reason = NULL;
7802
7803                 /* Count what is on board. */
7804                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7805
7806                 /* Some material-based adjudications that have to be made before stalemate test */
7807                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7808                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7809                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7810                      if(canAdjudicate && appData.checkMates) {
7811                          if(engineOpponent)
7812                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7813                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7814                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7815                          return 1;
7816                      }
7817                 }
7818
7819                 /* Bare King in Shatranj (loses) or Losers (wins) */
7820                 if( nrW == 1 || nrB == 1) {
7821                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7822                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7823                      if(canAdjudicate && appData.checkMates) {
7824                          if(engineOpponent)
7825                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7826                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7827                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7828                          return 1;
7829                      }
7830                   } else
7831                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7832                   {    /* bare King */
7833                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7834                         if(canAdjudicate && appData.checkMates) {
7835                             /* but only adjudicate if adjudication enabled */
7836                             if(engineOpponent)
7837                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7838                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7839                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7840                             return 1;
7841                         }
7842                   }
7843                 } else bare = 1;
7844
7845
7846             // don't wait for engine to announce game end if we can judge ourselves
7847             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7848               case MT_CHECK:
7849                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7850                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7851                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7852                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7853                             checkCnt++;
7854                         if(checkCnt >= 2) {
7855                             reason = "Xboard adjudication: 3rd check";
7856                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7857                             break;
7858                         }
7859                     }
7860                 }
7861               case MT_NONE:
7862               default:
7863                 break;
7864               case MT_STALEMATE:
7865               case MT_STAINMATE:
7866                 reason = "Xboard adjudication: Stalemate";
7867                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7868                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7869                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7870                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7871                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7872                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7873                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7874                                                                         EP_CHECKMATE : EP_WINS);
7875                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7876                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7877                 }
7878                 break;
7879               case MT_CHECKMATE:
7880                 reason = "Xboard adjudication: Checkmate";
7881                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7882                 if(gameInfo.variant == VariantShogi) {
7883                     if(forwardMostMove > backwardMostMove
7884                        && moveList[forwardMostMove-1][1] == '@'
7885                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7886                         reason = "XBoard adjudication: pawn-drop mate";
7887                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7888                     }
7889                 }
7890                 break;
7891             }
7892
7893                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7894                     case EP_STALEMATE:
7895                         result = GameIsDrawn; break;
7896                     case EP_CHECKMATE:
7897                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7898                     case EP_WINS:
7899                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7900                     default:
7901                         result = EndOfFile;
7902                 }
7903                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7904                     if(engineOpponent)
7905                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7906                     GameEnds( result, reason, GE_XBOARD );
7907                     return 1;
7908                 }
7909
7910                 /* Next absolutely insufficient mating material. */
7911                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7912                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7913                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7914
7915                      /* always flag draws, for judging claims */
7916                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7917
7918                      if(canAdjudicate && appData.materialDraws) {
7919                          /* but only adjudicate them if adjudication enabled */
7920                          if(engineOpponent) {
7921                            SendToProgram("force\n", engineOpponent); // suppress reply
7922                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7923                          }
7924                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7925                          return 1;
7926                      }
7927                 }
7928
7929                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7930                 if(gameInfo.variant == VariantXiangqi ?
7931                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7932                  : nrW + nrB == 4 &&
7933                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7934                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7935                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7936                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7937                    ) ) {
7938                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7939                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7940                           if(engineOpponent) {
7941                             SendToProgram("force\n", engineOpponent); // suppress reply
7942                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7943                           }
7944                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7945                           return 1;
7946                      }
7947                 } else moveCount = 6;
7948             }
7949
7950         // Repetition draws and 50-move rule can be applied independently of legality testing
7951
7952                 /* Check for rep-draws */
7953                 count = 0;
7954                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7955                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7956                 for(k = forwardMostMove-2;
7957                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7958                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7959                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7960                     k-=2)
7961                 {   int rights=0;
7962                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7963                         /* compare castling rights */
7964                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7965                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7966                                 rights++; /* King lost rights, while rook still had them */
7967                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7968                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7969                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7970                                    rights++; /* but at least one rook lost them */
7971                         }
7972                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7973                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7974                                 rights++;
7975                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7976                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7977                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7978                                    rights++;
7979                         }
7980                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7981                             && appData.drawRepeats > 1) {
7982                              /* adjudicate after user-specified nr of repeats */
7983                              int result = GameIsDrawn;
7984                              char *details = "XBoard adjudication: repetition draw";
7985                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7986                                 // [HGM] xiangqi: check for forbidden perpetuals
7987                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7988                                 for(m=forwardMostMove; m>k; m-=2) {
7989                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7990                                         ourPerpetual = 0; // the current mover did not always check
7991                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7992                                         hisPerpetual = 0; // the opponent did not always check
7993                                 }
7994                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7995                                                                         ourPerpetual, hisPerpetual);
7996                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7997                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7998                                     details = "Xboard adjudication: perpetual checking";
7999                                 } else
8000                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8001                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8002                                 } else
8003                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8004                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8005                                         result = BlackWins;
8006                                         details = "Xboard adjudication: repetition";
8007                                     }
8008                                 } else // it must be XQ
8009                                 // Now check for perpetual chases
8010                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8011                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8012                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8013                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8014                                         static char resdet[MSG_SIZ];
8015                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8016                                         details = resdet;
8017                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8018                                     } else
8019                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8020                                         break; // Abort repetition-checking loop.
8021                                 }
8022                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8023                              }
8024                              if(engineOpponent) {
8025                                SendToProgram("force\n", engineOpponent); // suppress reply
8026                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8027                              }
8028                              GameEnds( result, details, GE_XBOARD );
8029                              return 1;
8030                         }
8031                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8032                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8033                     }
8034                 }
8035
8036                 /* Now we test for 50-move draws. Determine ply count */
8037                 count = forwardMostMove;
8038                 /* look for last irreversble move */
8039                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8040                     count--;
8041                 /* if we hit starting position, add initial plies */
8042                 if( count == backwardMostMove )
8043                     count -= initialRulePlies;
8044                 count = forwardMostMove - count;
8045                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8046                         // adjust reversible move counter for checks in Xiangqi
8047                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8048                         if(i < backwardMostMove) i = backwardMostMove;
8049                         while(i <= forwardMostMove) {
8050                                 lastCheck = inCheck; // check evasion does not count
8051                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8052                                 if(inCheck || lastCheck) count--; // check does not count
8053                                 i++;
8054                         }
8055                 }
8056                 if( count >= 100)
8057                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8058                          /* this is used to judge if draw claims are legal */
8059                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8060                          if(engineOpponent) {
8061                            SendToProgram("force\n", engineOpponent); // suppress reply
8062                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8063                          }
8064                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8065                          return 1;
8066                 }
8067
8068                 /* if draw offer is pending, treat it as a draw claim
8069                  * when draw condition present, to allow engines a way to
8070                  * claim draws before making their move to avoid a race
8071                  * condition occurring after their move
8072                  */
8073                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8074                          char *p = NULL;
8075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8076                              p = "Draw claim: 50-move rule";
8077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8078                              p = "Draw claim: 3-fold repetition";
8079                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8080                              p = "Draw claim: insufficient mating material";
8081                          if( p != NULL && canAdjudicate) {
8082                              if(engineOpponent) {
8083                                SendToProgram("force\n", engineOpponent); // suppress reply
8084                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8085                              }
8086                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8087                              return 1;
8088                          }
8089                 }
8090
8091                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8092                     if(engineOpponent) {
8093                       SendToProgram("force\n", engineOpponent); // suppress reply
8094                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8095                     }
8096                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8097                     return 1;
8098                 }
8099         return 0;
8100 }
8101
8102 char *
8103 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8104 {   // [HGM] book: this routine intercepts moves to simulate book replies
8105     char *bookHit = NULL;
8106
8107     //first determine if the incoming move brings opponent into his book
8108     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8109         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8110     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8111     if(bookHit != NULL && !cps->bookSuspend) {
8112         // make sure opponent is not going to reply after receiving move to book position
8113         SendToProgram("force\n", cps);
8114         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8115     }
8116     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8117     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8118     // now arrange restart after book miss
8119     if(bookHit) {
8120         // after a book hit we never send 'go', and the code after the call to this routine
8121         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8122         char buf[MSG_SIZ], *move = bookHit;
8123         if(cps->useSAN) {
8124             int fromX, fromY, toX, toY;
8125             char promoChar;
8126             ChessMove moveType;
8127             move = buf + 30;
8128             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8129                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8130                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8131                                     PosFlags(forwardMostMove),
8132                                     fromY, fromX, toY, toX, promoChar, move);
8133             } else {
8134                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8135                 bookHit = NULL;
8136             }
8137         }
8138         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8139         SendToProgram(buf, cps);
8140         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8141     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8142         SendToProgram("go\n", cps);
8143         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8144     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8145         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8146             SendToProgram("go\n", cps);
8147         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8148     }
8149     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8150 }
8151
8152 int
8153 LoadError (char *errmess, ChessProgramState *cps)
8154 {   // unloads engine and switches back to -ncp mode if it was first
8155     if(cps->initDone) return FALSE;
8156     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8157     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8158     cps->pr = NoProc;
8159     if(cps == &first) {
8160         appData.noChessProgram = TRUE;
8161         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8162         gameMode = BeginningOfGame; ModeHighlight();
8163         SetNCPMode();
8164     }
8165     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8166     DisplayMessage("", ""); // erase waiting message
8167     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8168     return TRUE;
8169 }
8170
8171 char *savedMessage;
8172 ChessProgramState *savedState;
8173 void
8174 DeferredBookMove (void)
8175 {
8176         if(savedState->lastPing != savedState->lastPong)
8177                     ScheduleDelayedEvent(DeferredBookMove, 10);
8178         else
8179         HandleMachineMove(savedMessage, savedState);
8180 }
8181
8182 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8183 static ChessProgramState *stalledEngine;
8184 static char stashedInputMove[MSG_SIZ];
8185
8186 void
8187 HandleMachineMove (char *message, ChessProgramState *cps)
8188 {
8189     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8190     char realname[MSG_SIZ];
8191     int fromX, fromY, toX, toY;
8192     ChessMove moveType;
8193     char promoChar;
8194     char *p, *pv=buf1;
8195     int machineWhite, oldError;
8196     char *bookHit;
8197
8198     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8199         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8200         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8201             DisplayError(_("Invalid pairing from pairing engine"), 0);
8202             return;
8203         }
8204         pairingReceived = 1;
8205         NextMatchGame();
8206         return; // Skim the pairing messages here.
8207     }
8208
8209     oldError = cps->userError; cps->userError = 0;
8210
8211 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8212     /*
8213      * Kludge to ignore BEL characters
8214      */
8215     while (*message == '\007') message++;
8216
8217     /*
8218      * [HGM] engine debug message: ignore lines starting with '#' character
8219      */
8220     if(cps->debug && *message == '#') return;
8221
8222     /*
8223      * Look for book output
8224      */
8225     if (cps == &first && bookRequested) {
8226         if (message[0] == '\t' || message[0] == ' ') {
8227             /* Part of the book output is here; append it */
8228             strcat(bookOutput, message);
8229             strcat(bookOutput, "  \n");
8230             return;
8231         } else if (bookOutput[0] != NULLCHAR) {
8232             /* All of book output has arrived; display it */
8233             char *p = bookOutput;
8234             while (*p != NULLCHAR) {
8235                 if (*p == '\t') *p = ' ';
8236                 p++;
8237             }
8238             DisplayInformation(bookOutput);
8239             bookRequested = FALSE;
8240             /* Fall through to parse the current output */
8241         }
8242     }
8243
8244     /*
8245      * Look for machine move.
8246      */
8247     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8248         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8249     {
8250         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8251             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8252             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8253             stalledEngine = cps;
8254             if(appData.ponderNextMove) { // bring opponent out of ponder
8255                 if(gameMode == TwoMachinesPlay) {
8256                     if(cps->other->pause)
8257                         PauseEngine(cps->other);
8258                     else
8259                         SendToProgram("easy\n", cps->other);
8260                 }
8261             }
8262             StopClocks();
8263             return;
8264         }
8265
8266         /* This method is only useful on engines that support ping */
8267         if (cps->lastPing != cps->lastPong) {
8268           if (gameMode == BeginningOfGame) {
8269             /* Extra move from before last new; ignore */
8270             if (appData.debugMode) {
8271                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8272             }
8273           } else {
8274             if (appData.debugMode) {
8275                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8276                         cps->which, gameMode);
8277             }
8278
8279             SendToProgram("undo\n", cps);
8280           }
8281           return;
8282         }
8283
8284         switch (gameMode) {
8285           case BeginningOfGame:
8286             /* Extra move from before last reset; ignore */
8287             if (appData.debugMode) {
8288                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8289             }
8290             return;
8291
8292           case EndOfGame:
8293           case IcsIdle:
8294           default:
8295             /* Extra move after we tried to stop.  The mode test is
8296                not a reliable way of detecting this problem, but it's
8297                the best we can do on engines that don't support ping.
8298             */
8299             if (appData.debugMode) {
8300                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8301                         cps->which, gameMode);
8302             }
8303             SendToProgram("undo\n", cps);
8304             return;
8305
8306           case MachinePlaysWhite:
8307           case IcsPlayingWhite:
8308             machineWhite = TRUE;
8309             break;
8310
8311           case MachinePlaysBlack:
8312           case IcsPlayingBlack:
8313             machineWhite = FALSE;
8314             break;
8315
8316           case TwoMachinesPlay:
8317             machineWhite = (cps->twoMachinesColor[0] == 'w');
8318             break;
8319         }
8320         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8321             if (appData.debugMode) {
8322                 fprintf(debugFP,
8323                         "Ignoring move out of turn by %s, gameMode %d"
8324                         ", forwardMost %d\n",
8325                         cps->which, gameMode, forwardMostMove);
8326             }
8327             return;
8328         }
8329
8330         if(cps->alphaRank) AlphaRank(machineMove, 4);
8331         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8332                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8333             /* Machine move could not be parsed; ignore it. */
8334           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8335                     machineMove, _(cps->which));
8336             DisplayMoveError(buf1);
8337             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8338                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8339             if (gameMode == TwoMachinesPlay) {
8340               GameEnds(machineWhite ? BlackWins : WhiteWins,
8341                        buf1, GE_XBOARD);
8342             }
8343             return;
8344         }
8345
8346         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8347         /* So we have to redo legality test with true e.p. status here,  */
8348         /* to make sure an illegal e.p. capture does not slip through,   */
8349         /* to cause a forfeit on a justified illegal-move complaint      */
8350         /* of the opponent.                                              */
8351         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8352            ChessMove moveType;
8353            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8354                              fromY, fromX, toY, toX, promoChar);
8355             if(moveType == IllegalMove) {
8356               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8357                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8358                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8359                            buf1, GE_XBOARD);
8360                 return;
8361            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8362            /* [HGM] Kludge to handle engines that send FRC-style castling
8363               when they shouldn't (like TSCP-Gothic) */
8364            switch(moveType) {
8365              case WhiteASideCastleFR:
8366              case BlackASideCastleFR:
8367                toX+=2;
8368                currentMoveString[2]++;
8369                break;
8370              case WhiteHSideCastleFR:
8371              case BlackHSideCastleFR:
8372                toX--;
8373                currentMoveString[2]--;
8374                break;
8375              default: ; // nothing to do, but suppresses warning of pedantic compilers
8376            }
8377         }
8378         hintRequested = FALSE;
8379         lastHint[0] = NULLCHAR;
8380         bookRequested = FALSE;
8381         /* Program may be pondering now */
8382         cps->maybeThinking = TRUE;
8383         if (cps->sendTime == 2) cps->sendTime = 1;
8384         if (cps->offeredDraw) cps->offeredDraw--;
8385
8386         /* [AS] Save move info*/
8387         pvInfoList[ forwardMostMove ].score = programStats.score;
8388         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8389         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8390
8391         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8392
8393         /* Test suites abort the 'game' after one move */
8394         if(*appData.finger) {
8395            static FILE *f;
8396            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8397            if(!f) f = fopen(appData.finger, "w");
8398            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8399            else { DisplayFatalError("Bad output file", errno, 0); return; }
8400            free(fen);
8401            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8402         }
8403
8404         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8405         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8406             int count = 0;
8407
8408             while( count < adjudicateLossPlies ) {
8409                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8410
8411                 if( count & 1 ) {
8412                     score = -score; /* Flip score for winning side */
8413                 }
8414
8415                 if( score > adjudicateLossThreshold ) {
8416                     break;
8417                 }
8418
8419                 count++;
8420             }
8421
8422             if( count >= adjudicateLossPlies ) {
8423                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8424
8425                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8426                     "Xboard adjudication",
8427                     GE_XBOARD );
8428
8429                 return;
8430             }
8431         }
8432
8433         if(Adjudicate(cps)) {
8434             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8435             return; // [HGM] adjudicate: for all automatic game ends
8436         }
8437
8438 #if ZIPPY
8439         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8440             first.initDone) {
8441           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8442                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8443                 SendToICS("draw ");
8444                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8445           }
8446           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8447           ics_user_moved = 1;
8448           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8449                 char buf[3*MSG_SIZ];
8450
8451                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8452                         programStats.score / 100.,
8453                         programStats.depth,
8454                         programStats.time / 100.,
8455                         (unsigned int)programStats.nodes,
8456                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8457                         programStats.movelist);
8458                 SendToICS(buf);
8459 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8460           }
8461         }
8462 #endif
8463
8464         /* [AS] Clear stats for next move */
8465         ClearProgramStats();
8466         thinkOutput[0] = NULLCHAR;
8467         hiddenThinkOutputState = 0;
8468
8469         bookHit = NULL;
8470         if (gameMode == TwoMachinesPlay) {
8471             /* [HGM] relaying draw offers moved to after reception of move */
8472             /* and interpreting offer as claim if it brings draw condition */
8473             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8474                 SendToProgram("draw\n", cps->other);
8475             }
8476             if (cps->other->sendTime) {
8477                 SendTimeRemaining(cps->other,
8478                                   cps->other->twoMachinesColor[0] == 'w');
8479             }
8480             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8481             if (firstMove && !bookHit) {
8482                 firstMove = FALSE;
8483                 if (cps->other->useColors) {
8484                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8485                 }
8486                 SendToProgram("go\n", cps->other);
8487             }
8488             cps->other->maybeThinking = TRUE;
8489         }
8490
8491         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8492
8493         if (!pausing && appData.ringBellAfterMoves) {
8494             RingBell();
8495         }
8496
8497         /*
8498          * Reenable menu items that were disabled while
8499          * machine was thinking
8500          */
8501         if (gameMode != TwoMachinesPlay)
8502             SetUserThinkingEnables();
8503
8504         // [HGM] book: after book hit opponent has received move and is now in force mode
8505         // force the book reply into it, and then fake that it outputted this move by jumping
8506         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8507         if(bookHit) {
8508                 static char bookMove[MSG_SIZ]; // a bit generous?
8509
8510                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8511                 strcat(bookMove, bookHit);
8512                 message = bookMove;
8513                 cps = cps->other;
8514                 programStats.nodes = programStats.depth = programStats.time =
8515                 programStats.score = programStats.got_only_move = 0;
8516                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8517
8518                 if(cps->lastPing != cps->lastPong) {
8519                     savedMessage = message; // args for deferred call
8520                     savedState = cps;
8521                     ScheduleDelayedEvent(DeferredBookMove, 10);
8522                     return;
8523                 }
8524                 goto FakeBookMove;
8525         }
8526
8527         return;
8528     }
8529
8530     /* Set special modes for chess engines.  Later something general
8531      *  could be added here; for now there is just one kludge feature,
8532      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8533      *  when "xboard" is given as an interactive command.
8534      */
8535     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8536         cps->useSigint = FALSE;
8537         cps->useSigterm = FALSE;
8538     }
8539     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8540       ParseFeatures(message+8, cps);
8541       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8542     }
8543
8544     if (!strncmp(message, "setup ", 6) && 
8545         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8546                                         ) { // [HGM] allow first engine to define opening position
8547       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8548       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8549       *buf = NULLCHAR;
8550       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8551       if(startedFromSetupPosition) return;
8552       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8553       if(dummy >= 3) {
8554         while(message[s] && message[s++] != ' ');
8555         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8556            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8557             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8558             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8559           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8560           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8561         }
8562       }
8563       ParseFEN(boards[0], &dummy, message+s);
8564       DrawPosition(TRUE, boards[0]);
8565       startedFromSetupPosition = TRUE;
8566       return;
8567     }
8568     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8569      * want this, I was asked to put it in, and obliged.
8570      */
8571     if (!strncmp(message, "setboard ", 9)) {
8572         Board initial_position;
8573
8574         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8575
8576         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8577             DisplayError(_("Bad FEN received from engine"), 0);
8578             return ;
8579         } else {
8580            Reset(TRUE, FALSE);
8581            CopyBoard(boards[0], initial_position);
8582            initialRulePlies = FENrulePlies;
8583            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8584            else gameMode = MachinePlaysBlack;
8585            DrawPosition(FALSE, boards[currentMove]);
8586         }
8587         return;
8588     }
8589
8590     /*
8591      * Look for communication commands
8592      */
8593     if (!strncmp(message, "telluser ", 9)) {
8594         if(message[9] == '\\' && message[10] == '\\')
8595             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8596         PlayTellSound();
8597         DisplayNote(message + 9);
8598         return;
8599     }
8600     if (!strncmp(message, "tellusererror ", 14)) {
8601         cps->userError = 1;
8602         if(message[14] == '\\' && message[15] == '\\')
8603             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8604         PlayTellSound();
8605         DisplayError(message + 14, 0);
8606         return;
8607     }
8608     if (!strncmp(message, "tellopponent ", 13)) {
8609       if (appData.icsActive) {
8610         if (loggedOn) {
8611           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8612           SendToICS(buf1);
8613         }
8614       } else {
8615         DisplayNote(message + 13);
8616       }
8617       return;
8618     }
8619     if (!strncmp(message, "tellothers ", 11)) {
8620       if (appData.icsActive) {
8621         if (loggedOn) {
8622           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8623           SendToICS(buf1);
8624         }
8625       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8626       return;
8627     }
8628     if (!strncmp(message, "tellall ", 8)) {
8629       if (appData.icsActive) {
8630         if (loggedOn) {
8631           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8632           SendToICS(buf1);
8633         }
8634       } else {
8635         DisplayNote(message + 8);
8636       }
8637       return;
8638     }
8639     if (strncmp(message, "warning", 7) == 0) {
8640         /* Undocumented feature, use tellusererror in new code */
8641         DisplayError(message, 0);
8642         return;
8643     }
8644     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8645         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8646         strcat(realname, " query");
8647         AskQuestion(realname, buf2, buf1, cps->pr);
8648         return;
8649     }
8650     /* Commands from the engine directly to ICS.  We don't allow these to be
8651      *  sent until we are logged on. Crafty kibitzes have been known to
8652      *  interfere with the login process.
8653      */
8654     if (loggedOn) {
8655         if (!strncmp(message, "tellics ", 8)) {
8656             SendToICS(message + 8);
8657             SendToICS("\n");
8658             return;
8659         }
8660         if (!strncmp(message, "tellicsnoalias ", 15)) {
8661             SendToICS(ics_prefix);
8662             SendToICS(message + 15);
8663             SendToICS("\n");
8664             return;
8665         }
8666         /* The following are for backward compatibility only */
8667         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8668             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8669             SendToICS(ics_prefix);
8670             SendToICS(message);
8671             SendToICS("\n");
8672             return;
8673         }
8674     }
8675     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8676         return;
8677     }
8678     if(!strncmp(message, "highlight ", 10)) {
8679         if(appData.testLegality && appData.markers) return;
8680         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8681         return;
8682     }
8683     if(!strncmp(message, "click ", 6)) {
8684         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8685         if(appData.testLegality || !appData.oneClick) return;
8686         sscanf(message+6, "%c%d%c", &f, &y, &c);
8687         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8688         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8689         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8690         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8691         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8692         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8693             LeftClick(Release, lastLeftX, lastLeftY);
8694         controlKey  = (c == ',');
8695         LeftClick(Press, x, y);
8696         LeftClick(Release, x, y);
8697         first.highlight = f;
8698         return;
8699     }
8700     /*
8701      * If the move is illegal, cancel it and redraw the board.
8702      * Also deal with other error cases.  Matching is rather loose
8703      * here to accommodate engines written before the spec.
8704      */
8705     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8706         strncmp(message, "Error", 5) == 0) {
8707         if (StrStr(message, "name") ||
8708             StrStr(message, "rating") || StrStr(message, "?") ||
8709             StrStr(message, "result") || StrStr(message, "board") ||
8710             StrStr(message, "bk") || StrStr(message, "computer") ||
8711             StrStr(message, "variant") || StrStr(message, "hint") ||
8712             StrStr(message, "random") || StrStr(message, "depth") ||
8713             StrStr(message, "accepted")) {
8714             return;
8715         }
8716         if (StrStr(message, "protover")) {
8717           /* Program is responding to input, so it's apparently done
8718              initializing, and this error message indicates it is
8719              protocol version 1.  So we don't need to wait any longer
8720              for it to initialize and send feature commands. */
8721           FeatureDone(cps, 1);
8722           cps->protocolVersion = 1;
8723           return;
8724         }
8725         cps->maybeThinking = FALSE;
8726
8727         if (StrStr(message, "draw")) {
8728             /* Program doesn't have "draw" command */
8729             cps->sendDrawOffers = 0;
8730             return;
8731         }
8732         if (cps->sendTime != 1 &&
8733             (StrStr(message, "time") || StrStr(message, "otim"))) {
8734           /* Program apparently doesn't have "time" or "otim" command */
8735           cps->sendTime = 0;
8736           return;
8737         }
8738         if (StrStr(message, "analyze")) {
8739             cps->analysisSupport = FALSE;
8740             cps->analyzing = FALSE;
8741 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8742             EditGameEvent(); // [HGM] try to preserve loaded game
8743             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8744             DisplayError(buf2, 0);
8745             return;
8746         }
8747         if (StrStr(message, "(no matching move)st")) {
8748           /* Special kludge for GNU Chess 4 only */
8749           cps->stKludge = TRUE;
8750           SendTimeControl(cps, movesPerSession, timeControl,
8751                           timeIncrement, appData.searchDepth,
8752                           searchTime);
8753           return;
8754         }
8755         if (StrStr(message, "(no matching move)sd")) {
8756           /* Special kludge for GNU Chess 4 only */
8757           cps->sdKludge = TRUE;
8758           SendTimeControl(cps, movesPerSession, timeControl,
8759                           timeIncrement, appData.searchDepth,
8760                           searchTime);
8761           return;
8762         }
8763         if (!StrStr(message, "llegal")) {
8764             return;
8765         }
8766         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8767             gameMode == IcsIdle) return;
8768         if (forwardMostMove <= backwardMostMove) return;
8769         if (pausing) PauseEvent();
8770       if(appData.forceIllegal) {
8771             // [HGM] illegal: machine refused move; force position after move into it
8772           SendToProgram("force\n", cps);
8773           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8774                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8775                 // when black is to move, while there might be nothing on a2 or black
8776                 // might already have the move. So send the board as if white has the move.
8777                 // But first we must change the stm of the engine, as it refused the last move
8778                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8779                 if(WhiteOnMove(forwardMostMove)) {
8780                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8781                     SendBoard(cps, forwardMostMove); // kludgeless board
8782                 } else {
8783                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8784                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8785                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8786                 }
8787           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8788             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8789                  gameMode == TwoMachinesPlay)
8790               SendToProgram("go\n", cps);
8791             return;
8792       } else
8793         if (gameMode == PlayFromGameFile) {
8794             /* Stop reading this game file */
8795             gameMode = EditGame;
8796             ModeHighlight();
8797         }
8798         /* [HGM] illegal-move claim should forfeit game when Xboard */
8799         /* only passes fully legal moves                            */
8800         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8801             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8802                                 "False illegal-move claim", GE_XBOARD );
8803             return; // do not take back move we tested as valid
8804         }
8805         currentMove = forwardMostMove-1;
8806         DisplayMove(currentMove-1); /* before DisplayMoveError */
8807         SwitchClocks(forwardMostMove-1); // [HGM] race
8808         DisplayBothClocks();
8809         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8810                 parseList[currentMove], _(cps->which));
8811         DisplayMoveError(buf1);
8812         DrawPosition(FALSE, boards[currentMove]);
8813
8814         SetUserThinkingEnables();
8815         return;
8816     }
8817     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8818         /* Program has a broken "time" command that
8819            outputs a string not ending in newline.
8820            Don't use it. */
8821         cps->sendTime = 0;
8822     }
8823
8824     /*
8825      * If chess program startup fails, exit with an error message.
8826      * Attempts to recover here are futile. [HGM] Well, we try anyway
8827      */
8828     if ((StrStr(message, "unknown host") != NULL)
8829         || (StrStr(message, "No remote directory") != NULL)
8830         || (StrStr(message, "not found") != NULL)
8831         || (StrStr(message, "No such file") != NULL)
8832         || (StrStr(message, "can't alloc") != NULL)
8833         || (StrStr(message, "Permission denied") != NULL)) {
8834
8835         cps->maybeThinking = FALSE;
8836         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8837                 _(cps->which), cps->program, cps->host, message);
8838         RemoveInputSource(cps->isr);
8839         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8840             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8841             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8842         }
8843         return;
8844     }
8845
8846     /*
8847      * Look for hint output
8848      */
8849     if (sscanf(message, "Hint: %s", buf1) == 1) {
8850         if (cps == &first && hintRequested) {
8851             hintRequested = FALSE;
8852             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8853                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8854                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8855                                     PosFlags(forwardMostMove),
8856                                     fromY, fromX, toY, toX, promoChar, buf1);
8857                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8858                 DisplayInformation(buf2);
8859             } else {
8860                 /* Hint move could not be parsed!? */
8861               snprintf(buf2, sizeof(buf2),
8862                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8863                         buf1, _(cps->which));
8864                 DisplayError(buf2, 0);
8865             }
8866         } else {
8867           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8868         }
8869         return;
8870     }
8871
8872     /*
8873      * Ignore other messages if game is not in progress
8874      */
8875     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8876         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8877
8878     /*
8879      * look for win, lose, draw, or draw offer
8880      */
8881     if (strncmp(message, "1-0", 3) == 0) {
8882         char *p, *q, *r = "";
8883         p = strchr(message, '{');
8884         if (p) {
8885             q = strchr(p, '}');
8886             if (q) {
8887                 *q = NULLCHAR;
8888                 r = p + 1;
8889             }
8890         }
8891         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8892         return;
8893     } else if (strncmp(message, "0-1", 3) == 0) {
8894         char *p, *q, *r = "";
8895         p = strchr(message, '{');
8896         if (p) {
8897             q = strchr(p, '}');
8898             if (q) {
8899                 *q = NULLCHAR;
8900                 r = p + 1;
8901             }
8902         }
8903         /* Kludge for Arasan 4.1 bug */
8904         if (strcmp(r, "Black resigns") == 0) {
8905             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8906             return;
8907         }
8908         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8909         return;
8910     } else if (strncmp(message, "1/2", 3) == 0) {
8911         char *p, *q, *r = "";
8912         p = strchr(message, '{');
8913         if (p) {
8914             q = strchr(p, '}');
8915             if (q) {
8916                 *q = NULLCHAR;
8917                 r = p + 1;
8918             }
8919         }
8920
8921         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8922         return;
8923
8924     } else if (strncmp(message, "White resign", 12) == 0) {
8925         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8926         return;
8927     } else if (strncmp(message, "Black resign", 12) == 0) {
8928         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8929         return;
8930     } else if (strncmp(message, "White matches", 13) == 0 ||
8931                strncmp(message, "Black matches", 13) == 0   ) {
8932         /* [HGM] ignore GNUShogi noises */
8933         return;
8934     } else if (strncmp(message, "White", 5) == 0 &&
8935                message[5] != '(' &&
8936                StrStr(message, "Black") == NULL) {
8937         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8938         return;
8939     } else if (strncmp(message, "Black", 5) == 0 &&
8940                message[5] != '(') {
8941         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8942         return;
8943     } else if (strcmp(message, "resign") == 0 ||
8944                strcmp(message, "computer resigns") == 0) {
8945         switch (gameMode) {
8946           case MachinePlaysBlack:
8947           case IcsPlayingBlack:
8948             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8949             break;
8950           case MachinePlaysWhite:
8951           case IcsPlayingWhite:
8952             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8953             break;
8954           case TwoMachinesPlay:
8955             if (cps->twoMachinesColor[0] == 'w')
8956               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8957             else
8958               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8959             break;
8960           default:
8961             /* can't happen */
8962             break;
8963         }
8964         return;
8965     } else if (strncmp(message, "opponent mates", 14) == 0) {
8966         switch (gameMode) {
8967           case MachinePlaysBlack:
8968           case IcsPlayingBlack:
8969             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8970             break;
8971           case MachinePlaysWhite:
8972           case IcsPlayingWhite:
8973             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8974             break;
8975           case TwoMachinesPlay:
8976             if (cps->twoMachinesColor[0] == 'w')
8977               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8978             else
8979               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8980             break;
8981           default:
8982             /* can't happen */
8983             break;
8984         }
8985         return;
8986     } else if (strncmp(message, "computer mates", 14) == 0) {
8987         switch (gameMode) {
8988           case MachinePlaysBlack:
8989           case IcsPlayingBlack:
8990             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8991             break;
8992           case MachinePlaysWhite:
8993           case IcsPlayingWhite:
8994             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8995             break;
8996           case TwoMachinesPlay:
8997             if (cps->twoMachinesColor[0] == 'w')
8998               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8999             else
9000               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9001             break;
9002           default:
9003             /* can't happen */
9004             break;
9005         }
9006         return;
9007     } else if (strncmp(message, "checkmate", 9) == 0) {
9008         if (WhiteOnMove(forwardMostMove)) {
9009             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9010         } else {
9011             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9012         }
9013         return;
9014     } else if (strstr(message, "Draw") != NULL ||
9015                strstr(message, "game is a draw") != NULL) {
9016         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9017         return;
9018     } else if (strstr(message, "offer") != NULL &&
9019                strstr(message, "draw") != NULL) {
9020 #if ZIPPY
9021         if (appData.zippyPlay && first.initDone) {
9022             /* Relay offer to ICS */
9023             SendToICS(ics_prefix);
9024             SendToICS("draw\n");
9025         }
9026 #endif
9027         cps->offeredDraw = 2; /* valid until this engine moves twice */
9028         if (gameMode == TwoMachinesPlay) {
9029             if (cps->other->offeredDraw) {
9030                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9031             /* [HGM] in two-machine mode we delay relaying draw offer      */
9032             /* until after we also have move, to see if it is really claim */
9033             }
9034         } else if (gameMode == MachinePlaysWhite ||
9035                    gameMode == MachinePlaysBlack) {
9036           if (userOfferedDraw) {
9037             DisplayInformation(_("Machine accepts your draw offer"));
9038             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9039           } else {
9040             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9041           }
9042         }
9043     }
9044
9045
9046     /*
9047      * Look for thinking output
9048      */
9049     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9050           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9051                                 ) {
9052         int plylev, mvleft, mvtot, curscore, time;
9053         char mvname[MOVE_LEN];
9054         u64 nodes; // [DM]
9055         char plyext;
9056         int ignore = FALSE;
9057         int prefixHint = FALSE;
9058         mvname[0] = NULLCHAR;
9059
9060         switch (gameMode) {
9061           case MachinePlaysBlack:
9062           case IcsPlayingBlack:
9063             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9064             break;
9065           case MachinePlaysWhite:
9066           case IcsPlayingWhite:
9067             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9068             break;
9069           case AnalyzeMode:
9070           case AnalyzeFile:
9071             break;
9072           case IcsObserving: /* [DM] icsEngineAnalyze */
9073             if (!appData.icsEngineAnalyze) ignore = TRUE;
9074             break;
9075           case TwoMachinesPlay:
9076             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9077                 ignore = TRUE;
9078             }
9079             break;
9080           default:
9081             ignore = TRUE;
9082             break;
9083         }
9084
9085         if (!ignore) {
9086             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9087             buf1[0] = NULLCHAR;
9088             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9089                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9090
9091                 if (plyext != ' ' && plyext != '\t') {
9092                     time *= 100;
9093                 }
9094
9095                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9096                 if( cps->scoreIsAbsolute &&
9097                     ( gameMode == MachinePlaysBlack ||
9098                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9099                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9100                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9101                      !WhiteOnMove(currentMove)
9102                     ) )
9103                 {
9104                     curscore = -curscore;
9105                 }
9106
9107                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9108
9109                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9110                         char buf[MSG_SIZ];
9111                         FILE *f;
9112                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9113                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9114                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9115                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9116                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9117                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9118                                 fclose(f);
9119                         } else DisplayError(_("failed writing PV"), 0);
9120                 }
9121
9122                 tempStats.depth = plylev;
9123                 tempStats.nodes = nodes;
9124                 tempStats.time = time;
9125                 tempStats.score = curscore;
9126                 tempStats.got_only_move = 0;
9127
9128                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9129                         int ticklen;
9130
9131                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9132                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9133                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9134                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9135                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9136                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9137                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9138                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9139                 }
9140
9141                 /* Buffer overflow protection */
9142                 if (pv[0] != NULLCHAR) {
9143                     if (strlen(pv) >= sizeof(tempStats.movelist)
9144                         && appData.debugMode) {
9145                         fprintf(debugFP,
9146                                 "PV is too long; using the first %u bytes.\n",
9147                                 (unsigned) sizeof(tempStats.movelist) - 1);
9148                     }
9149
9150                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9151                 } else {
9152                     sprintf(tempStats.movelist, " no PV\n");
9153                 }
9154
9155                 if (tempStats.seen_stat) {
9156                     tempStats.ok_to_send = 1;
9157                 }
9158
9159                 if (strchr(tempStats.movelist, '(') != NULL) {
9160                     tempStats.line_is_book = 1;
9161                     tempStats.nr_moves = 0;
9162                     tempStats.moves_left = 0;
9163                 } else {
9164                     tempStats.line_is_book = 0;
9165                 }
9166
9167                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9168                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9169
9170                 SendProgramStatsToFrontend( cps, &tempStats );
9171
9172                 /*
9173                     [AS] Protect the thinkOutput buffer from overflow... this
9174                     is only useful if buf1 hasn't overflowed first!
9175                 */
9176                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9177                          plylev,
9178                          (gameMode == TwoMachinesPlay ?
9179                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9180                          ((double) curscore) / 100.0,
9181                          prefixHint ? lastHint : "",
9182                          prefixHint ? " " : "" );
9183
9184                 if( buf1[0] != NULLCHAR ) {
9185                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9186
9187                     if( strlen(pv) > max_len ) {
9188                         if( appData.debugMode) {
9189                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9190                         }
9191                         pv[max_len+1] = '\0';
9192                     }
9193
9194                     strcat( thinkOutput, pv);
9195                 }
9196
9197                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9198                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9199                     DisplayMove(currentMove - 1);
9200                 }
9201                 return;
9202
9203             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9204                 /* crafty (9.25+) says "(only move) <move>"
9205                  * if there is only 1 legal move
9206                  */
9207                 sscanf(p, "(only move) %s", buf1);
9208                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9209                 sprintf(programStats.movelist, "%s (only move)", buf1);
9210                 programStats.depth = 1;
9211                 programStats.nr_moves = 1;
9212                 programStats.moves_left = 1;
9213                 programStats.nodes = 1;
9214                 programStats.time = 1;
9215                 programStats.got_only_move = 1;
9216
9217                 /* Not really, but we also use this member to
9218                    mean "line isn't going to change" (Crafty
9219                    isn't searching, so stats won't change) */
9220                 programStats.line_is_book = 1;
9221
9222                 SendProgramStatsToFrontend( cps, &programStats );
9223
9224                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9225                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9226                     DisplayMove(currentMove - 1);
9227                 }
9228                 return;
9229             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9230                               &time, &nodes, &plylev, &mvleft,
9231                               &mvtot, mvname) >= 5) {
9232                 /* The stat01: line is from Crafty (9.29+) in response
9233                    to the "." command */
9234                 programStats.seen_stat = 1;
9235                 cps->maybeThinking = TRUE;
9236
9237                 if (programStats.got_only_move || !appData.periodicUpdates)
9238                   return;
9239
9240                 programStats.depth = plylev;
9241                 programStats.time = time;
9242                 programStats.nodes = nodes;
9243                 programStats.moves_left = mvleft;
9244                 programStats.nr_moves = mvtot;
9245                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9246                 programStats.ok_to_send = 1;
9247                 programStats.movelist[0] = '\0';
9248
9249                 SendProgramStatsToFrontend( cps, &programStats );
9250
9251                 return;
9252
9253             } else if (strncmp(message,"++",2) == 0) {
9254                 /* Crafty 9.29+ outputs this */
9255                 programStats.got_fail = 2;
9256                 return;
9257
9258             } else if (strncmp(message,"--",2) == 0) {
9259                 /* Crafty 9.29+ outputs this */
9260                 programStats.got_fail = 1;
9261                 return;
9262
9263             } else if (thinkOutput[0] != NULLCHAR &&
9264                        strncmp(message, "    ", 4) == 0) {
9265                 unsigned message_len;
9266
9267                 p = message;
9268                 while (*p && *p == ' ') p++;
9269
9270                 message_len = strlen( p );
9271
9272                 /* [AS] Avoid buffer overflow */
9273                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9274                     strcat(thinkOutput, " ");
9275                     strcat(thinkOutput, p);
9276                 }
9277
9278                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9279                     strcat(programStats.movelist, " ");
9280                     strcat(programStats.movelist, p);
9281                 }
9282
9283                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9284                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9285                     DisplayMove(currentMove - 1);
9286                 }
9287                 return;
9288             }
9289         }
9290         else {
9291             buf1[0] = NULLCHAR;
9292
9293             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9294                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9295             {
9296                 ChessProgramStats cpstats;
9297
9298                 if (plyext != ' ' && plyext != '\t') {
9299                     time *= 100;
9300                 }
9301
9302                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9303                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9304                     curscore = -curscore;
9305                 }
9306
9307                 cpstats.depth = plylev;
9308                 cpstats.nodes = nodes;
9309                 cpstats.time = time;
9310                 cpstats.score = curscore;
9311                 cpstats.got_only_move = 0;
9312                 cpstats.movelist[0] = '\0';
9313
9314                 if (buf1[0] != NULLCHAR) {
9315                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9316                 }
9317
9318                 cpstats.ok_to_send = 0;
9319                 cpstats.line_is_book = 0;
9320                 cpstats.nr_moves = 0;
9321                 cpstats.moves_left = 0;
9322
9323                 SendProgramStatsToFrontend( cps, &cpstats );
9324             }
9325         }
9326     }
9327 }
9328
9329
9330 /* Parse a game score from the character string "game", and
9331    record it as the history of the current game.  The game
9332    score is NOT assumed to start from the standard position.
9333    The display is not updated in any way.
9334    */
9335 void
9336 ParseGameHistory (char *game)
9337 {
9338     ChessMove moveType;
9339     int fromX, fromY, toX, toY, boardIndex;
9340     char promoChar;
9341     char *p, *q;
9342     char buf[MSG_SIZ];
9343
9344     if (appData.debugMode)
9345       fprintf(debugFP, "Parsing game history: %s\n", game);
9346
9347     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9348     gameInfo.site = StrSave(appData.icsHost);
9349     gameInfo.date = PGNDate();
9350     gameInfo.round = StrSave("-");
9351
9352     /* Parse out names of players */
9353     while (*game == ' ') game++;
9354     p = buf;
9355     while (*game != ' ') *p++ = *game++;
9356     *p = NULLCHAR;
9357     gameInfo.white = StrSave(buf);
9358     while (*game == ' ') game++;
9359     p = buf;
9360     while (*game != ' ' && *game != '\n') *p++ = *game++;
9361     *p = NULLCHAR;
9362     gameInfo.black = StrSave(buf);
9363
9364     /* Parse moves */
9365     boardIndex = blackPlaysFirst ? 1 : 0;
9366     yynewstr(game);
9367     for (;;) {
9368         yyboardindex = boardIndex;
9369         moveType = (ChessMove) Myylex();
9370         switch (moveType) {
9371           case IllegalMove:             /* maybe suicide chess, etc. */
9372   if (appData.debugMode) {
9373     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9374     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9375     setbuf(debugFP, NULL);
9376   }
9377           case WhitePromotion:
9378           case BlackPromotion:
9379           case WhiteNonPromotion:
9380           case BlackNonPromotion:
9381           case NormalMove:
9382           case WhiteCapturesEnPassant:
9383           case BlackCapturesEnPassant:
9384           case WhiteKingSideCastle:
9385           case WhiteQueenSideCastle:
9386           case BlackKingSideCastle:
9387           case BlackQueenSideCastle:
9388           case WhiteKingSideCastleWild:
9389           case WhiteQueenSideCastleWild:
9390           case BlackKingSideCastleWild:
9391           case BlackQueenSideCastleWild:
9392           /* PUSH Fabien */
9393           case WhiteHSideCastleFR:
9394           case WhiteASideCastleFR:
9395           case BlackHSideCastleFR:
9396           case BlackASideCastleFR:
9397           /* POP Fabien */
9398             fromX = currentMoveString[0] - AAA;
9399             fromY = currentMoveString[1] - ONE;
9400             toX = currentMoveString[2] - AAA;
9401             toY = currentMoveString[3] - ONE;
9402             promoChar = currentMoveString[4];
9403             break;
9404           case WhiteDrop:
9405           case BlackDrop:
9406             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9407             fromX = moveType == WhiteDrop ?
9408               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9409             (int) CharToPiece(ToLower(currentMoveString[0]));
9410             fromY = DROP_RANK;
9411             toX = currentMoveString[2] - AAA;
9412             toY = currentMoveString[3] - ONE;
9413             promoChar = NULLCHAR;
9414             break;
9415           case AmbiguousMove:
9416             /* bug? */
9417             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9418   if (appData.debugMode) {
9419     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9420     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9421     setbuf(debugFP, NULL);
9422   }
9423             DisplayError(buf, 0);
9424             return;
9425           case ImpossibleMove:
9426             /* bug? */
9427             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9428   if (appData.debugMode) {
9429     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9430     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9431     setbuf(debugFP, NULL);
9432   }
9433             DisplayError(buf, 0);
9434             return;
9435           case EndOfFile:
9436             if (boardIndex < backwardMostMove) {
9437                 /* Oops, gap.  How did that happen? */
9438                 DisplayError(_("Gap in move list"), 0);
9439                 return;
9440             }
9441             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9442             if (boardIndex > forwardMostMove) {
9443                 forwardMostMove = boardIndex;
9444             }
9445             return;
9446           case ElapsedTime:
9447             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9448                 strcat(parseList[boardIndex-1], " ");
9449                 strcat(parseList[boardIndex-1], yy_text);
9450             }
9451             continue;
9452           case Comment:
9453           case PGNTag:
9454           case NAG:
9455           default:
9456             /* ignore */
9457             continue;
9458           case WhiteWins:
9459           case BlackWins:
9460           case GameIsDrawn:
9461           case GameUnfinished:
9462             if (gameMode == IcsExamining) {
9463                 if (boardIndex < backwardMostMove) {
9464                     /* Oops, gap.  How did that happen? */
9465                     return;
9466                 }
9467                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9468                 return;
9469             }
9470             gameInfo.result = moveType;
9471             p = strchr(yy_text, '{');
9472             if (p == NULL) p = strchr(yy_text, '(');
9473             if (p == NULL) {
9474                 p = yy_text;
9475                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9476             } else {
9477                 q = strchr(p, *p == '{' ? '}' : ')');
9478                 if (q != NULL) *q = NULLCHAR;
9479                 p++;
9480             }
9481             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9482             gameInfo.resultDetails = StrSave(p);
9483             continue;
9484         }
9485         if (boardIndex >= forwardMostMove &&
9486             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9487             backwardMostMove = blackPlaysFirst ? 1 : 0;
9488             return;
9489         }
9490         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9491                                  fromY, fromX, toY, toX, promoChar,
9492                                  parseList[boardIndex]);
9493         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9494         /* currentMoveString is set as a side-effect of yylex */
9495         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9496         strcat(moveList[boardIndex], "\n");
9497         boardIndex++;
9498         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9499         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9500           case MT_NONE:
9501           case MT_STALEMATE:
9502           default:
9503             break;
9504           case MT_CHECK:
9505             if(gameInfo.variant != VariantShogi)
9506                 strcat(parseList[boardIndex - 1], "+");
9507             break;
9508           case MT_CHECKMATE:
9509           case MT_STAINMATE:
9510             strcat(parseList[boardIndex - 1], "#");
9511             break;
9512         }
9513     }
9514 }
9515
9516
9517 /* Apply a move to the given board  */
9518 void
9519 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9520 {
9521   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9522   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9523
9524     /* [HGM] compute & store e.p. status and castling rights for new position */
9525     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9526
9527       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9528       oldEP = (signed char)board[EP_STATUS];
9529       board[EP_STATUS] = EP_NONE;
9530
9531   if (fromY == DROP_RANK) {
9532         /* must be first */
9533         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9534             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9535             return;
9536         }
9537         piece = board[toY][toX] = (ChessSquare) fromX;
9538   } else {
9539       int i;
9540
9541       if( board[toY][toX] != EmptySquare )
9542            board[EP_STATUS] = EP_CAPTURE;
9543
9544       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9545            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9546                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9547       } else
9548       if( board[fromY][fromX] == WhitePawn ) {
9549            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9550                board[EP_STATUS] = EP_PAWN_MOVE;
9551            if( toY-fromY==2) {
9552                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9553                         gameInfo.variant != VariantBerolina || toX < fromX)
9554                       board[EP_STATUS] = toX | berolina;
9555                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9556                         gameInfo.variant != VariantBerolina || toX > fromX)
9557                       board[EP_STATUS] = toX;
9558            }
9559       } else
9560       if( board[fromY][fromX] == BlackPawn ) {
9561            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9562                board[EP_STATUS] = EP_PAWN_MOVE;
9563            if( toY-fromY== -2) {
9564                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9565                         gameInfo.variant != VariantBerolina || toX < fromX)
9566                       board[EP_STATUS] = toX | berolina;
9567                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9568                         gameInfo.variant != VariantBerolina || toX > fromX)
9569                       board[EP_STATUS] = toX;
9570            }
9571        }
9572
9573        for(i=0; i<nrCastlingRights; i++) {
9574            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9575               board[CASTLING][i] == toX   && castlingRank[i] == toY
9576              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9577        }
9578
9579        if(gameInfo.variant == VariantSChess) { // update virginity
9580            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9581            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9582            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9583            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9584        }
9585
9586      if (fromX == toX && fromY == toY) return;
9587
9588      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9589      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9590      if(gameInfo.variant == VariantKnightmate)
9591          king += (int) WhiteUnicorn - (int) WhiteKing;
9592
9593     /* Code added by Tord: */
9594     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9595     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9596         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9597       board[fromY][fromX] = EmptySquare;
9598       board[toY][toX] = EmptySquare;
9599       if((toX > fromX) != (piece == WhiteRook)) {
9600         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9601       } else {
9602         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9603       }
9604     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9605                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9606       board[fromY][fromX] = EmptySquare;
9607       board[toY][toX] = EmptySquare;
9608       if((toX > fromX) != (piece == BlackRook)) {
9609         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9610       } else {
9611         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9612       }
9613     /* End of code added by Tord */
9614
9615     } else if (board[fromY][fromX] == king
9616         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9617         && toY == fromY && toX > fromX+1) {
9618         board[fromY][fromX] = EmptySquare;
9619         board[toY][toX] = king;
9620         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9621         board[fromY][BOARD_RGHT-1] = EmptySquare;
9622     } else if (board[fromY][fromX] == king
9623         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9624                && toY == fromY && toX < fromX-1) {
9625         board[fromY][fromX] = EmptySquare;
9626         board[toY][toX] = king;
9627         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9628         board[fromY][BOARD_LEFT] = EmptySquare;
9629     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9630                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9631                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9632                ) {
9633         /* white pawn promotion */
9634         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9635         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9636             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9637         board[fromY][fromX] = EmptySquare;
9638     } else if ((fromY >= BOARD_HEIGHT>>1)
9639                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9640                && (toX != fromX)
9641                && gameInfo.variant != VariantXiangqi
9642                && gameInfo.variant != VariantBerolina
9643                && (board[fromY][fromX] == WhitePawn)
9644                && (board[toY][toX] == EmptySquare)) {
9645         board[fromY][fromX] = EmptySquare;
9646         board[toY][toX] = WhitePawn;
9647         captured = board[toY - 1][toX];
9648         board[toY - 1][toX] = EmptySquare;
9649     } else if ((fromY == BOARD_HEIGHT-4)
9650                && (toX == fromX)
9651                && gameInfo.variant == VariantBerolina
9652                && (board[fromY][fromX] == WhitePawn)
9653                && (board[toY][toX] == EmptySquare)) {
9654         board[fromY][fromX] = EmptySquare;
9655         board[toY][toX] = WhitePawn;
9656         if(oldEP & EP_BEROLIN_A) {
9657                 captured = board[fromY][fromX-1];
9658                 board[fromY][fromX-1] = EmptySquare;
9659         }else{  captured = board[fromY][fromX+1];
9660                 board[fromY][fromX+1] = EmptySquare;
9661         }
9662     } else if (board[fromY][fromX] == king
9663         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9664                && toY == fromY && toX > fromX+1) {
9665         board[fromY][fromX] = EmptySquare;
9666         board[toY][toX] = king;
9667         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9668         board[fromY][BOARD_RGHT-1] = EmptySquare;
9669     } else if (board[fromY][fromX] == king
9670         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9671                && toY == fromY && toX < fromX-1) {
9672         board[fromY][fromX] = EmptySquare;
9673         board[toY][toX] = king;
9674         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9675         board[fromY][BOARD_LEFT] = EmptySquare;
9676     } else if (fromY == 7 && fromX == 3
9677                && board[fromY][fromX] == BlackKing
9678                && toY == 7 && toX == 5) {
9679         board[fromY][fromX] = EmptySquare;
9680         board[toY][toX] = BlackKing;
9681         board[fromY][7] = EmptySquare;
9682         board[toY][4] = BlackRook;
9683     } else if (fromY == 7 && fromX == 3
9684                && board[fromY][fromX] == BlackKing
9685                && toY == 7 && toX == 1) {
9686         board[fromY][fromX] = EmptySquare;
9687         board[toY][toX] = BlackKing;
9688         board[fromY][0] = EmptySquare;
9689         board[toY][2] = BlackRook;
9690     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9691                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9692                && toY < promoRank && promoChar
9693                ) {
9694         /* black pawn promotion */
9695         board[toY][toX] = CharToPiece(ToLower(promoChar));
9696         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9697             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9698         board[fromY][fromX] = EmptySquare;
9699     } else if ((fromY < BOARD_HEIGHT>>1)
9700                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9701                && (toX != fromX)
9702                && gameInfo.variant != VariantXiangqi
9703                && gameInfo.variant != VariantBerolina
9704                && (board[fromY][fromX] == BlackPawn)
9705                && (board[toY][toX] == EmptySquare)) {
9706         board[fromY][fromX] = EmptySquare;
9707         board[toY][toX] = BlackPawn;
9708         captured = board[toY + 1][toX];
9709         board[toY + 1][toX] = EmptySquare;
9710     } else if ((fromY == 3)
9711                && (toX == fromX)
9712                && gameInfo.variant == VariantBerolina
9713                && (board[fromY][fromX] == BlackPawn)
9714                && (board[toY][toX] == EmptySquare)) {
9715         board[fromY][fromX] = EmptySquare;
9716         board[toY][toX] = BlackPawn;
9717         if(oldEP & EP_BEROLIN_A) {
9718                 captured = board[fromY][fromX-1];
9719                 board[fromY][fromX-1] = EmptySquare;
9720         }else{  captured = board[fromY][fromX+1];
9721                 board[fromY][fromX+1] = EmptySquare;
9722         }
9723     } else {
9724         board[toY][toX] = board[fromY][fromX];
9725         board[fromY][fromX] = EmptySquare;
9726     }
9727   }
9728
9729     if (gameInfo.holdingsWidth != 0) {
9730
9731       /* !!A lot more code needs to be written to support holdings  */
9732       /* [HGM] OK, so I have written it. Holdings are stored in the */
9733       /* penultimate board files, so they are automaticlly stored   */
9734       /* in the game history.                                       */
9735       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9736                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9737         /* Delete from holdings, by decreasing count */
9738         /* and erasing image if necessary            */
9739         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9740         if(p < (int) BlackPawn) { /* white drop */
9741              p -= (int)WhitePawn;
9742                  p = PieceToNumber((ChessSquare)p);
9743              if(p >= gameInfo.holdingsSize) p = 0;
9744              if(--board[p][BOARD_WIDTH-2] <= 0)
9745                   board[p][BOARD_WIDTH-1] = EmptySquare;
9746              if((int)board[p][BOARD_WIDTH-2] < 0)
9747                         board[p][BOARD_WIDTH-2] = 0;
9748         } else {                  /* black drop */
9749              p -= (int)BlackPawn;
9750                  p = PieceToNumber((ChessSquare)p);
9751              if(p >= gameInfo.holdingsSize) p = 0;
9752              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9753                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9754              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9755                         board[BOARD_HEIGHT-1-p][1] = 0;
9756         }
9757       }
9758       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9759           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9760         /* [HGM] holdings: Add to holdings, if holdings exist */
9761         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9762                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9763                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9764         }
9765         p = (int) captured;
9766         if (p >= (int) BlackPawn) {
9767           p -= (int)BlackPawn;
9768           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9769                   /* in Shogi restore piece to its original  first */
9770                   captured = (ChessSquare) (DEMOTED captured);
9771                   p = DEMOTED p;
9772           }
9773           p = PieceToNumber((ChessSquare)p);
9774           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9775           board[p][BOARD_WIDTH-2]++;
9776           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9777         } else {
9778           p -= (int)WhitePawn;
9779           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9780                   captured = (ChessSquare) (DEMOTED captured);
9781                   p = DEMOTED p;
9782           }
9783           p = PieceToNumber((ChessSquare)p);
9784           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9785           board[BOARD_HEIGHT-1-p][1]++;
9786           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9787         }
9788       }
9789     } else if (gameInfo.variant == VariantAtomic) {
9790       if (captured != EmptySquare) {
9791         int y, x;
9792         for (y = toY-1; y <= toY+1; y++) {
9793           for (x = toX-1; x <= toX+1; x++) {
9794             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9795                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9796               board[y][x] = EmptySquare;
9797             }
9798           }
9799         }
9800         board[toY][toX] = EmptySquare;
9801       }
9802     }
9803     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9804         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9805     } else
9806     if(promoChar == '+') {
9807         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9808         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9809     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9810         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9811         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9812            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9813         board[toY][toX] = newPiece;
9814     }
9815     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9816                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9817         // [HGM] superchess: take promotion piece out of holdings
9818         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9819         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9820             if(!--board[k][BOARD_WIDTH-2])
9821                 board[k][BOARD_WIDTH-1] = EmptySquare;
9822         } else {
9823             if(!--board[BOARD_HEIGHT-1-k][1])
9824                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9825         }
9826     }
9827
9828 }
9829
9830 /* Updates forwardMostMove */
9831 void
9832 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9833 {
9834 //    forwardMostMove++; // [HGM] bare: moved downstream
9835
9836     (void) CoordsToAlgebraic(boards[forwardMostMove],
9837                              PosFlags(forwardMostMove),
9838                              fromY, fromX, toY, toX, promoChar,
9839                              parseList[forwardMostMove]);
9840
9841     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9842         int timeLeft; static int lastLoadFlag=0; int king, piece;
9843         piece = boards[forwardMostMove][fromY][fromX];
9844         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9845         if(gameInfo.variant == VariantKnightmate)
9846             king += (int) WhiteUnicorn - (int) WhiteKing;
9847         if(forwardMostMove == 0) {
9848             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9849                 fprintf(serverMoves, "%s;", UserName());
9850             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9851                 fprintf(serverMoves, "%s;", second.tidy);
9852             fprintf(serverMoves, "%s;", first.tidy);
9853             if(gameMode == MachinePlaysWhite)
9854                 fprintf(serverMoves, "%s;", UserName());
9855             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9856                 fprintf(serverMoves, "%s;", second.tidy);
9857         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9858         lastLoadFlag = loadFlag;
9859         // print base move
9860         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9861         // print castling suffix
9862         if( toY == fromY && piece == king ) {
9863             if(toX-fromX > 1)
9864                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9865             if(fromX-toX >1)
9866                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9867         }
9868         // e.p. suffix
9869         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9870              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9871              boards[forwardMostMove][toY][toX] == EmptySquare
9872              && fromX != toX && fromY != toY)
9873                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9874         // promotion suffix
9875         if(promoChar != NULLCHAR) {
9876             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9877                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9878                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9879             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9880         }
9881         if(!loadFlag) {
9882                 char buf[MOVE_LEN*2], *p; int len;
9883             fprintf(serverMoves, "/%d/%d",
9884                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9885             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9886             else                      timeLeft = blackTimeRemaining/1000;
9887             fprintf(serverMoves, "/%d", timeLeft);
9888                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9889                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9890                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9891                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9892             fprintf(serverMoves, "/%s", buf);
9893         }
9894         fflush(serverMoves);
9895     }
9896
9897     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9898         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9899       return;
9900     }
9901     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9902     if (commentList[forwardMostMove+1] != NULL) {
9903         free(commentList[forwardMostMove+1]);
9904         commentList[forwardMostMove+1] = NULL;
9905     }
9906     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9907     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9908     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9909     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9910     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9911     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9912     adjustedClock = FALSE;
9913     gameInfo.result = GameUnfinished;
9914     if (gameInfo.resultDetails != NULL) {
9915         free(gameInfo.resultDetails);
9916         gameInfo.resultDetails = NULL;
9917     }
9918     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9919                               moveList[forwardMostMove - 1]);
9920     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9921       case MT_NONE:
9922       case MT_STALEMATE:
9923       default:
9924         break;
9925       case MT_CHECK:
9926         if(gameInfo.variant != VariantShogi)
9927             strcat(parseList[forwardMostMove - 1], "+");
9928         break;
9929       case MT_CHECKMATE:
9930       case MT_STAINMATE:
9931         strcat(parseList[forwardMostMove - 1], "#");
9932         break;
9933     }
9934
9935 }
9936
9937 /* Updates currentMove if not pausing */
9938 void
9939 ShowMove (int fromX, int fromY, int toX, int toY)
9940 {
9941     int instant = (gameMode == PlayFromGameFile) ?
9942         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9943     if(appData.noGUI) return;
9944     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9945         if (!instant) {
9946             if (forwardMostMove == currentMove + 1) {
9947                 AnimateMove(boards[forwardMostMove - 1],
9948                             fromX, fromY, toX, toY);
9949             }
9950         }
9951         currentMove = forwardMostMove;
9952     }
9953
9954     if (instant) return;
9955
9956     DisplayMove(currentMove - 1);
9957     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9958             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9959                 SetHighlights(fromX, fromY, toX, toY);
9960             }
9961     }
9962     DrawPosition(FALSE, boards[currentMove]);
9963     DisplayBothClocks();
9964     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9965 }
9966
9967 void
9968 SendEgtPath (ChessProgramState *cps)
9969 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9970         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9971
9972         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9973
9974         while(*p) {
9975             char c, *q = name+1, *r, *s;
9976
9977             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9978             while(*p && *p != ',') *q++ = *p++;
9979             *q++ = ':'; *q = 0;
9980             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9981                 strcmp(name, ",nalimov:") == 0 ) {
9982                 // take nalimov path from the menu-changeable option first, if it is defined
9983               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9984                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9985             } else
9986             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9987                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9988                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9989                 s = r = StrStr(s, ":") + 1; // beginning of path info
9990                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9991                 c = *r; *r = 0;             // temporarily null-terminate path info
9992                     *--q = 0;               // strip of trailig ':' from name
9993                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9994                 *r = c;
9995                 SendToProgram(buf,cps);     // send egtbpath command for this format
9996             }
9997             if(*p == ',') p++; // read away comma to position for next format name
9998         }
9999 }
10000
10001 static int
10002 NonStandardBoardSize ()
10003 {
10004       /* [HGM] Awkward testing. Should really be a table */
10005       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10006       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10007       if( gameInfo.variant == VariantXiangqi )
10008            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10009       if( gameInfo.variant == VariantShogi )
10010            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10011       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10012            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10013       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10014           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10015            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10016       if( gameInfo.variant == VariantCourier )
10017            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10018       if( gameInfo.variant == VariantSuper )
10019            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10020       if( gameInfo.variant == VariantGreat )
10021            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10022       if( gameInfo.variant == VariantSChess )
10023            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10024       if( gameInfo.variant == VariantGrand )
10025            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10026       return overruled;
10027 }
10028
10029 void
10030 InitChessProgram (ChessProgramState *cps, int setup)
10031 /* setup needed to setup FRC opening position */
10032 {
10033     char buf[MSG_SIZ], b[MSG_SIZ];
10034     if (appData.noChessProgram) return;
10035     hintRequested = FALSE;
10036     bookRequested = FALSE;
10037
10038     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10039     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10040     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10041     if(cps->memSize) { /* [HGM] memory */
10042       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10043         SendToProgram(buf, cps);
10044     }
10045     SendEgtPath(cps); /* [HGM] EGT */
10046     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10047       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10048         SendToProgram(buf, cps);
10049     }
10050
10051     SendToProgram(cps->initString, cps);
10052     if (gameInfo.variant != VariantNormal &&
10053         gameInfo.variant != VariantLoadable
10054         /* [HGM] also send variant if board size non-standard */
10055         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10056                                             ) {
10057       char *v = VariantName(gameInfo.variant);
10058       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10059         /* [HGM] in protocol 1 we have to assume all variants valid */
10060         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10061         DisplayFatalError(buf, 0, 1);
10062         return;
10063       }
10064
10065       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10066         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10067                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10068            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10069            if(StrStr(cps->variants, b) == NULL) {
10070                // specific sized variant not known, check if general sizing allowed
10071                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10072                    if(StrStr(cps->variants, "boardsize") == NULL) {
10073                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10074                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10075                        DisplayFatalError(buf, 0, 1);
10076                        return;
10077                    }
10078                    /* [HGM] here we really should compare with the maximum supported board size */
10079                }
10080            }
10081       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10082       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10083       SendToProgram(buf, cps);
10084     }
10085     currentlyInitializedVariant = gameInfo.variant;
10086
10087     /* [HGM] send opening position in FRC to first engine */
10088     if(setup) {
10089           SendToProgram("force\n", cps);
10090           SendBoard(cps, 0);
10091           /* engine is now in force mode! Set flag to wake it up after first move. */
10092           setboardSpoiledMachineBlack = 1;
10093     }
10094
10095     if (cps->sendICS) {
10096       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10097       SendToProgram(buf, cps);
10098     }
10099     cps->maybeThinking = FALSE;
10100     cps->offeredDraw = 0;
10101     if (!appData.icsActive) {
10102         SendTimeControl(cps, movesPerSession, timeControl,
10103                         timeIncrement, appData.searchDepth,
10104                         searchTime);
10105     }
10106     if (appData.showThinking
10107         // [HGM] thinking: four options require thinking output to be sent
10108         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10109                                 ) {
10110         SendToProgram("post\n", cps);
10111     }
10112     SendToProgram("hard\n", cps);
10113     if (!appData.ponderNextMove) {
10114         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10115            it without being sure what state we are in first.  "hard"
10116            is not a toggle, so that one is OK.
10117          */
10118         SendToProgram("easy\n", cps);
10119     }
10120     if (cps->usePing) {
10121       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10122       SendToProgram(buf, cps);
10123     }
10124     cps->initDone = TRUE;
10125     ClearEngineOutputPane(cps == &second);
10126 }
10127
10128
10129 void
10130 ResendOptions (ChessProgramState *cps)
10131 { // send the stored value of the options
10132   int i;
10133   char buf[MSG_SIZ];
10134   Option *opt = cps->option;
10135   for(i=0; i<cps->nrOptions; i++, opt++) {
10136       switch(opt->type) {
10137         case Spin:
10138         case Slider:
10139         case CheckBox:
10140             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10141           break;
10142         case ComboBox:
10143           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10144           break;
10145         default:
10146             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10147           break;
10148         case Button:
10149         case SaveButton:
10150           continue;
10151       }
10152       SendToProgram(buf, cps);
10153   }
10154 }
10155
10156 void
10157 StartChessProgram (ChessProgramState *cps)
10158 {
10159     char buf[MSG_SIZ];
10160     int err;
10161
10162     if (appData.noChessProgram) return;
10163     cps->initDone = FALSE;
10164
10165     if (strcmp(cps->host, "localhost") == 0) {
10166         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10167     } else if (*appData.remoteShell == NULLCHAR) {
10168         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10169     } else {
10170         if (*appData.remoteUser == NULLCHAR) {
10171           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10172                     cps->program);
10173         } else {
10174           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10175                     cps->host, appData.remoteUser, cps->program);
10176         }
10177         err = StartChildProcess(buf, "", &cps->pr);
10178     }
10179
10180     if (err != 0) {
10181       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10182         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10183         if(cps != &first) return;
10184         appData.noChessProgram = TRUE;
10185         ThawUI();
10186         SetNCPMode();
10187 //      DisplayFatalError(buf, err, 1);
10188 //      cps->pr = NoProc;
10189 //      cps->isr = NULL;
10190         return;
10191     }
10192
10193     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10194     if (cps->protocolVersion > 1) {
10195       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10196       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10197         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10198         cps->comboCnt = 0;  //                and values of combo boxes
10199       }
10200       SendToProgram(buf, cps);
10201       if(cps->reload) ResendOptions(cps);
10202     } else {
10203       SendToProgram("xboard\n", cps);
10204     }
10205 }
10206
10207 void
10208 TwoMachinesEventIfReady P((void))
10209 {
10210   static int curMess = 0;
10211   if (first.lastPing != first.lastPong) {
10212     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10213     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10214     return;
10215   }
10216   if (second.lastPing != second.lastPong) {
10217     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10218     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10219     return;
10220   }
10221   DisplayMessage("", ""); curMess = 0;
10222   TwoMachinesEvent();
10223 }
10224
10225 char *
10226 MakeName (char *template)
10227 {
10228     time_t clock;
10229     struct tm *tm;
10230     static char buf[MSG_SIZ];
10231     char *p = buf;
10232     int i;
10233
10234     clock = time((time_t *)NULL);
10235     tm = localtime(&clock);
10236
10237     while(*p++ = *template++) if(p[-1] == '%') {
10238         switch(*template++) {
10239           case 0:   *p = 0; return buf;
10240           case 'Y': i = tm->tm_year+1900; break;
10241           case 'y': i = tm->tm_year-100; break;
10242           case 'M': i = tm->tm_mon+1; break;
10243           case 'd': i = tm->tm_mday; break;
10244           case 'h': i = tm->tm_hour; break;
10245           case 'm': i = tm->tm_min; break;
10246           case 's': i = tm->tm_sec; break;
10247           default:  i = 0;
10248         }
10249         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10250     }
10251     return buf;
10252 }
10253
10254 int
10255 CountPlayers (char *p)
10256 {
10257     int n = 0;
10258     while(p = strchr(p, '\n')) p++, n++; // count participants
10259     return n;
10260 }
10261
10262 FILE *
10263 WriteTourneyFile (char *results, FILE *f)
10264 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10265     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10266     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10267         // create a file with tournament description
10268         fprintf(f, "-participants {%s}\n", appData.participants);
10269         fprintf(f, "-seedBase %d\n", appData.seedBase);
10270         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10271         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10272         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10273         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10274         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10275         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10276         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10277         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10278         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10279         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10280         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10281         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10282         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10283         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10284         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10285         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10286         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10287         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10288         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10289         fprintf(f, "-smpCores %d\n", appData.smpCores);
10290         if(searchTime > 0)
10291                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10292         else {
10293                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10294                 fprintf(f, "-tc %s\n", appData.timeControl);
10295                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10296         }
10297         fprintf(f, "-results \"%s\"\n", results);
10298     }
10299     return f;
10300 }
10301
10302 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10303
10304 void
10305 Substitute (char *participants, int expunge)
10306 {
10307     int i, changed, changes=0, nPlayers=0;
10308     char *p, *q, *r, buf[MSG_SIZ];
10309     if(participants == NULL) return;
10310     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10311     r = p = participants; q = appData.participants;
10312     while(*p && *p == *q) {
10313         if(*p == '\n') r = p+1, nPlayers++;
10314         p++; q++;
10315     }
10316     if(*p) { // difference
10317         while(*p && *p++ != '\n');
10318         while(*q && *q++ != '\n');
10319       changed = nPlayers;
10320         changes = 1 + (strcmp(p, q) != 0);
10321     }
10322     if(changes == 1) { // a single engine mnemonic was changed
10323         q = r; while(*q) nPlayers += (*q++ == '\n');
10324         p = buf; while(*r && (*p = *r++) != '\n') p++;
10325         *p = NULLCHAR;
10326         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10327         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10328         if(mnemonic[i]) { // The substitute is valid
10329             FILE *f;
10330             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10331                 flock(fileno(f), LOCK_EX);
10332                 ParseArgsFromFile(f);
10333                 fseek(f, 0, SEEK_SET);
10334                 FREE(appData.participants); appData.participants = participants;
10335                 if(expunge) { // erase results of replaced engine
10336                     int len = strlen(appData.results), w, b, dummy;
10337                     for(i=0; i<len; i++) {
10338                         Pairing(i, nPlayers, &w, &b, &dummy);
10339                         if((w == changed || b == changed) && appData.results[i] == '*') {
10340                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10341                             fclose(f);
10342                             return;
10343                         }
10344                     }
10345                     for(i=0; i<len; i++) {
10346                         Pairing(i, nPlayers, &w, &b, &dummy);
10347                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10348                     }
10349                 }
10350                 WriteTourneyFile(appData.results, f);
10351                 fclose(f); // release lock
10352                 return;
10353             }
10354         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10355     }
10356     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10357     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10358     free(participants);
10359     return;
10360 }
10361
10362 int
10363 CheckPlayers (char *participants)
10364 {
10365         int i;
10366         char buf[MSG_SIZ], *p;
10367         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10368         while(p = strchr(participants, '\n')) {
10369             *p = NULLCHAR;
10370             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10371             if(!mnemonic[i]) {
10372                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10373                 *p = '\n';
10374                 DisplayError(buf, 0);
10375                 return 1;
10376             }
10377             *p = '\n';
10378             participants = p + 1;
10379         }
10380         return 0;
10381 }
10382
10383 int
10384 CreateTourney (char *name)
10385 {
10386         FILE *f;
10387         if(matchMode && strcmp(name, appData.tourneyFile)) {
10388              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10389         }
10390         if(name[0] == NULLCHAR) {
10391             if(appData.participants[0])
10392                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10393             return 0;
10394         }
10395         f = fopen(name, "r");
10396         if(f) { // file exists
10397             ASSIGN(appData.tourneyFile, name);
10398             ParseArgsFromFile(f); // parse it
10399         } else {
10400             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10401             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10402                 DisplayError(_("Not enough participants"), 0);
10403                 return 0;
10404             }
10405             if(CheckPlayers(appData.participants)) return 0;
10406             ASSIGN(appData.tourneyFile, name);
10407             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10408             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10409         }
10410         fclose(f);
10411         appData.noChessProgram = FALSE;
10412         appData.clockMode = TRUE;
10413         SetGNUMode();
10414         return 1;
10415 }
10416
10417 int
10418 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10419 {
10420     char buf[MSG_SIZ], *p, *q;
10421     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10422     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10423     skip = !all && group[0]; // if group requested, we start in skip mode
10424     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10425         p = names; q = buf; header = 0;
10426         while(*p && *p != '\n') *q++ = *p++;
10427         *q = 0;
10428         if(*p == '\n') p++;
10429         if(buf[0] == '#') {
10430             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10431             depth++; // we must be entering a new group
10432             if(all) continue; // suppress printing group headers when complete list requested
10433             header = 1;
10434             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10435         }
10436         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10437         if(engineList[i]) free(engineList[i]);
10438         engineList[i] = strdup(buf);
10439         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10440         if(engineMnemonic[i]) free(engineMnemonic[i]);
10441         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10442             strcat(buf, " (");
10443             sscanf(q + 8, "%s", buf + strlen(buf));
10444             strcat(buf, ")");
10445         }
10446         engineMnemonic[i] = strdup(buf);
10447         i++;
10448     }
10449     engineList[i] = engineMnemonic[i] = NULL;
10450     return i;
10451 }
10452
10453 // following implemented as macro to avoid type limitations
10454 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10455
10456 void
10457 SwapEngines (int n)
10458 {   // swap settings for first engine and other engine (so far only some selected options)
10459     int h;
10460     char *p;
10461     if(n == 0) return;
10462     SWAP(directory, p)
10463     SWAP(chessProgram, p)
10464     SWAP(isUCI, h)
10465     SWAP(hasOwnBookUCI, h)
10466     SWAP(protocolVersion, h)
10467     SWAP(reuse, h)
10468     SWAP(scoreIsAbsolute, h)
10469     SWAP(timeOdds, h)
10470     SWAP(logo, p)
10471     SWAP(pgnName, p)
10472     SWAP(pvSAN, h)
10473     SWAP(engOptions, p)
10474     SWAP(engInitString, p)
10475     SWAP(computerString, p)
10476     SWAP(features, p)
10477     SWAP(fenOverride, p)
10478     SWAP(NPS, h)
10479     SWAP(accumulateTC, h)
10480     SWAP(host, p)
10481 }
10482
10483 int
10484 GetEngineLine (char *s, int n)
10485 {
10486     int i;
10487     char buf[MSG_SIZ];
10488     extern char *icsNames;
10489     if(!s || !*s) return 0;
10490     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10491     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10492     if(!mnemonic[i]) return 0;
10493     if(n == 11) return 1; // just testing if there was a match
10494     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10495     if(n == 1) SwapEngines(n);
10496     ParseArgsFromString(buf);
10497     if(n == 1) SwapEngines(n);
10498     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10499         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10500         ParseArgsFromString(buf);
10501     }
10502     return 1;
10503 }
10504
10505 int
10506 SetPlayer (int player, char *p)
10507 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10508     int i;
10509     char buf[MSG_SIZ], *engineName;
10510     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10511     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10512     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10513     if(mnemonic[i]) {
10514         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10515         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10516         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10517         ParseArgsFromString(buf);
10518     } else { // no engine with this nickname is installed!
10519         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10520         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10521         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10522         ModeHighlight();
10523         DisplayError(buf, 0);
10524         return 0;
10525     }
10526     free(engineName);
10527     return i;
10528 }
10529
10530 char *recentEngines;
10531
10532 void
10533 RecentEngineEvent (int nr)
10534 {
10535     int n;
10536 //    SwapEngines(1); // bump first to second
10537 //    ReplaceEngine(&second, 1); // and load it there
10538     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10539     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10540     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10541         ReplaceEngine(&first, 0);
10542         FloatToFront(&appData.recentEngineList, command[n]);
10543     }
10544 }
10545
10546 int
10547 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10548 {   // determine players from game number
10549     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10550
10551     if(appData.tourneyType == 0) {
10552         roundsPerCycle = (nPlayers - 1) | 1;
10553         pairingsPerRound = nPlayers / 2;
10554     } else if(appData.tourneyType > 0) {
10555         roundsPerCycle = nPlayers - appData.tourneyType;
10556         pairingsPerRound = appData.tourneyType;
10557     }
10558     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10559     gamesPerCycle = gamesPerRound * roundsPerCycle;
10560     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10561     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10562     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10563     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10564     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10565     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10566
10567     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10568     if(appData.roundSync) *syncInterval = gamesPerRound;
10569
10570     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10571
10572     if(appData.tourneyType == 0) {
10573         if(curPairing == (nPlayers-1)/2 ) {
10574             *whitePlayer = curRound;
10575             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10576         } else {
10577             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10578             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10579             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10580             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10581         }
10582     } else if(appData.tourneyType > 1) {
10583         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10584         *whitePlayer = curRound + appData.tourneyType;
10585     } else if(appData.tourneyType > 0) {
10586         *whitePlayer = curPairing;
10587         *blackPlayer = curRound + appData.tourneyType;
10588     }
10589
10590     // take care of white/black alternation per round.
10591     // For cycles and games this is already taken care of by default, derived from matchGame!
10592     return curRound & 1;
10593 }
10594
10595 int
10596 NextTourneyGame (int nr, int *swapColors)
10597 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10598     char *p, *q;
10599     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10600     FILE *tf;
10601     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10602     tf = fopen(appData.tourneyFile, "r");
10603     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10604     ParseArgsFromFile(tf); fclose(tf);
10605     InitTimeControls(); // TC might be altered from tourney file
10606
10607     nPlayers = CountPlayers(appData.participants); // count participants
10608     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10609     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10610
10611     if(syncInterval) {
10612         p = q = appData.results;
10613         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10614         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10615             DisplayMessage(_("Waiting for other game(s)"),"");
10616             waitingForGame = TRUE;
10617             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10618             return 0;
10619         }
10620         waitingForGame = FALSE;
10621     }
10622
10623     if(appData.tourneyType < 0) {
10624         if(nr>=0 && !pairingReceived) {
10625             char buf[1<<16];
10626             if(pairing.pr == NoProc) {
10627                 if(!appData.pairingEngine[0]) {
10628                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10629                     return 0;
10630                 }
10631                 StartChessProgram(&pairing); // starts the pairing engine
10632             }
10633             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10634             SendToProgram(buf, &pairing);
10635             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10636             SendToProgram(buf, &pairing);
10637             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10638         }
10639         pairingReceived = 0;                              // ... so we continue here
10640         *swapColors = 0;
10641         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10642         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10643         matchGame = 1; roundNr = nr / syncInterval + 1;
10644     }
10645
10646     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10647
10648     // redefine engines, engine dir, etc.
10649     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10650     if(first.pr == NoProc) {
10651       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10652       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10653     }
10654     if(second.pr == NoProc) {
10655       SwapEngines(1);
10656       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10657       SwapEngines(1);         // and make that valid for second engine by swapping
10658       InitEngine(&second, 1);
10659     }
10660     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10661     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10662     return OK;
10663 }
10664
10665 void
10666 NextMatchGame ()
10667 {   // performs game initialization that does not invoke engines, and then tries to start the game
10668     int res, firstWhite, swapColors = 0;
10669     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10670     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
10671         char buf[MSG_SIZ];
10672         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10673         if(strcmp(buf, currentDebugFile)) { // name has changed
10674             FILE *f = fopen(buf, "w");
10675             if(f) { // if opening the new file failed, just keep using the old one
10676                 ASSIGN(currentDebugFile, buf);
10677                 fclose(debugFP);
10678                 debugFP = f;
10679             }
10680             if(appData.serverFileName) {
10681                 if(serverFP) fclose(serverFP);
10682                 serverFP = fopen(appData.serverFileName, "w");
10683                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10684                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10685             }
10686         }
10687     }
10688     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10689     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10690     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10691     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10692     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10693     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10694     Reset(FALSE, first.pr != NoProc);
10695     res = LoadGameOrPosition(matchGame); // setup game
10696     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10697     if(!res) return; // abort when bad game/pos file
10698     TwoMachinesEvent();
10699 }
10700
10701 void
10702 UserAdjudicationEvent (int result)
10703 {
10704     ChessMove gameResult = GameIsDrawn;
10705
10706     if( result > 0 ) {
10707         gameResult = WhiteWins;
10708     }
10709     else if( result < 0 ) {
10710         gameResult = BlackWins;
10711     }
10712
10713     if( gameMode == TwoMachinesPlay ) {
10714         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10715     }
10716 }
10717
10718
10719 // [HGM] save: calculate checksum of game to make games easily identifiable
10720 int
10721 StringCheckSum (char *s)
10722 {
10723         int i = 0;
10724         if(s==NULL) return 0;
10725         while(*s) i = i*259 + *s++;
10726         return i;
10727 }
10728
10729 int
10730 GameCheckSum ()
10731 {
10732         int i, sum=0;
10733         for(i=backwardMostMove; i<forwardMostMove; i++) {
10734                 sum += pvInfoList[i].depth;
10735                 sum += StringCheckSum(parseList[i]);
10736                 sum += StringCheckSum(commentList[i]);
10737                 sum *= 261;
10738         }
10739         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10740         return sum + StringCheckSum(commentList[i]);
10741 } // end of save patch
10742
10743 void
10744 GameEnds (ChessMove result, char *resultDetails, int whosays)
10745 {
10746     GameMode nextGameMode;
10747     int isIcsGame;
10748     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10749
10750     if(endingGame) return; /* [HGM] crash: forbid recursion */
10751     endingGame = 1;
10752     if(twoBoards) { // [HGM] dual: switch back to one board
10753         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10754         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10755     }
10756     if (appData.debugMode) {
10757       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10758               result, resultDetails ? resultDetails : "(null)", whosays);
10759     }
10760
10761     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10762
10763     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10764
10765     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10766         /* If we are playing on ICS, the server decides when the
10767            game is over, but the engine can offer to draw, claim
10768            a draw, or resign.
10769          */
10770 #if ZIPPY
10771         if (appData.zippyPlay && first.initDone) {
10772             if (result == GameIsDrawn) {
10773                 /* In case draw still needs to be claimed */
10774                 SendToICS(ics_prefix);
10775                 SendToICS("draw\n");
10776             } else if (StrCaseStr(resultDetails, "resign")) {
10777                 SendToICS(ics_prefix);
10778                 SendToICS("resign\n");
10779             }
10780         }
10781 #endif
10782         endingGame = 0; /* [HGM] crash */
10783         return;
10784     }
10785
10786     /* If we're loading the game from a file, stop */
10787     if (whosays == GE_FILE) {
10788       (void) StopLoadGameTimer();
10789       gameFileFP = NULL;
10790     }
10791
10792     /* Cancel draw offers */
10793     first.offeredDraw = second.offeredDraw = 0;
10794
10795     /* If this is an ICS game, only ICS can really say it's done;
10796        if not, anyone can. */
10797     isIcsGame = (gameMode == IcsPlayingWhite ||
10798                  gameMode == IcsPlayingBlack ||
10799                  gameMode == IcsObserving    ||
10800                  gameMode == IcsExamining);
10801
10802     if (!isIcsGame || whosays == GE_ICS) {
10803         /* OK -- not an ICS game, or ICS said it was done */
10804         StopClocks();
10805         if (!isIcsGame && !appData.noChessProgram)
10806           SetUserThinkingEnables();
10807
10808         /* [HGM] if a machine claims the game end we verify this claim */
10809         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10810             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10811                 char claimer;
10812                 ChessMove trueResult = (ChessMove) -1;
10813
10814                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10815                                             first.twoMachinesColor[0] :
10816                                             second.twoMachinesColor[0] ;
10817
10818                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10819                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10820                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10821                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10822                 } else
10823                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10824                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10825                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10826                 } else
10827                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10828                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10829                 }
10830
10831                 // now verify win claims, but not in drop games, as we don't understand those yet
10832                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10833                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10834                     (result == WhiteWins && claimer == 'w' ||
10835                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10836                       if (appData.debugMode) {
10837                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10838                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10839                       }
10840                       if(result != trueResult) {
10841                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10842                               result = claimer == 'w' ? BlackWins : WhiteWins;
10843                               resultDetails = buf;
10844                       }
10845                 } else
10846                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10847                     && (forwardMostMove <= backwardMostMove ||
10848                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10849                         (claimer=='b')==(forwardMostMove&1))
10850                                                                                   ) {
10851                       /* [HGM] verify: draws that were not flagged are false claims */
10852                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10853                       result = claimer == 'w' ? BlackWins : WhiteWins;
10854                       resultDetails = buf;
10855                 }
10856                 /* (Claiming a loss is accepted no questions asked!) */
10857             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10858                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10859                 result = GameUnfinished;
10860                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10861             }
10862             /* [HGM] bare: don't allow bare King to win */
10863             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10864                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10865                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10866                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10867                && result != GameIsDrawn)
10868             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10869                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10870                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10871                         if(p >= 0 && p <= (int)WhiteKing) k++;
10872                 }
10873                 if (appData.debugMode) {
10874                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10875                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10876                 }
10877                 if(k <= 1) {
10878                         result = GameIsDrawn;
10879                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10880                         resultDetails = buf;
10881                 }
10882             }
10883         }
10884
10885
10886         if(serverMoves != NULL && !loadFlag) { char c = '=';
10887             if(result==WhiteWins) c = '+';
10888             if(result==BlackWins) c = '-';
10889             if(resultDetails != NULL)
10890                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10891         }
10892         if (resultDetails != NULL) {
10893             gameInfo.result = result;
10894             gameInfo.resultDetails = StrSave(resultDetails);
10895
10896             /* display last move only if game was not loaded from file */
10897             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10898                 DisplayMove(currentMove - 1);
10899
10900             if (forwardMostMove != 0) {
10901                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10902                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10903                                                                 ) {
10904                     if (*appData.saveGameFile != NULLCHAR) {
10905                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10906                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10907                         else
10908                         SaveGameToFile(appData.saveGameFile, TRUE);
10909                     } else if (appData.autoSaveGames) {
10910                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10911                     }
10912                     if (*appData.savePositionFile != NULLCHAR) {
10913                         SavePositionToFile(appData.savePositionFile);
10914                     }
10915                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10916                 }
10917             }
10918
10919             /* Tell program how game ended in case it is learning */
10920             /* [HGM] Moved this to after saving the PGN, just in case */
10921             /* engine died and we got here through time loss. In that */
10922             /* case we will get a fatal error writing the pipe, which */
10923             /* would otherwise lose us the PGN.                       */
10924             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10925             /* output during GameEnds should never be fatal anymore   */
10926             if (gameMode == MachinePlaysWhite ||
10927                 gameMode == MachinePlaysBlack ||
10928                 gameMode == TwoMachinesPlay ||
10929                 gameMode == IcsPlayingWhite ||
10930                 gameMode == IcsPlayingBlack ||
10931                 gameMode == BeginningOfGame) {
10932                 char buf[MSG_SIZ];
10933                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10934                         resultDetails);
10935                 if (first.pr != NoProc) {
10936                     SendToProgram(buf, &first);
10937                 }
10938                 if (second.pr != NoProc &&
10939                     gameMode == TwoMachinesPlay) {
10940                     SendToProgram(buf, &second);
10941                 }
10942             }
10943         }
10944
10945         if (appData.icsActive) {
10946             if (appData.quietPlay &&
10947                 (gameMode == IcsPlayingWhite ||
10948                  gameMode == IcsPlayingBlack)) {
10949                 SendToICS(ics_prefix);
10950                 SendToICS("set shout 1\n");
10951             }
10952             nextGameMode = IcsIdle;
10953             ics_user_moved = FALSE;
10954             /* clean up premove.  It's ugly when the game has ended and the
10955              * premove highlights are still on the board.
10956              */
10957             if (gotPremove) {
10958               gotPremove = FALSE;
10959               ClearPremoveHighlights();
10960               DrawPosition(FALSE, boards[currentMove]);
10961             }
10962             if (whosays == GE_ICS) {
10963                 switch (result) {
10964                 case WhiteWins:
10965                     if (gameMode == IcsPlayingWhite)
10966                         PlayIcsWinSound();
10967                     else if(gameMode == IcsPlayingBlack)
10968                         PlayIcsLossSound();
10969                     break;
10970                 case BlackWins:
10971                     if (gameMode == IcsPlayingBlack)
10972                         PlayIcsWinSound();
10973                     else if(gameMode == IcsPlayingWhite)
10974                         PlayIcsLossSound();
10975                     break;
10976                 case GameIsDrawn:
10977                     PlayIcsDrawSound();
10978                     break;
10979                 default:
10980                     PlayIcsUnfinishedSound();
10981                 }
10982             }
10983             if(appData.quitNext) { ExitEvent(0); return; }
10984         } else if (gameMode == EditGame ||
10985                    gameMode == PlayFromGameFile ||
10986                    gameMode == AnalyzeMode ||
10987                    gameMode == AnalyzeFile) {
10988             nextGameMode = gameMode;
10989         } else {
10990             nextGameMode = EndOfGame;
10991         }
10992         pausing = FALSE;
10993         ModeHighlight();
10994     } else {
10995         nextGameMode = gameMode;
10996     }
10997
10998     if (appData.noChessProgram) {
10999         gameMode = nextGameMode;
11000         ModeHighlight();
11001         endingGame = 0; /* [HGM] crash */
11002         return;
11003     }
11004
11005     if (first.reuse) {
11006         /* Put first chess program into idle state */
11007         if (first.pr != NoProc &&
11008             (gameMode == MachinePlaysWhite ||
11009              gameMode == MachinePlaysBlack ||
11010              gameMode == TwoMachinesPlay ||
11011              gameMode == IcsPlayingWhite ||
11012              gameMode == IcsPlayingBlack ||
11013              gameMode == BeginningOfGame)) {
11014             SendToProgram("force\n", &first);
11015             if (first.usePing) {
11016               char buf[MSG_SIZ];
11017               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11018               SendToProgram(buf, &first);
11019             }
11020         }
11021     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11022         /* Kill off first chess program */
11023         if (first.isr != NULL)
11024           RemoveInputSource(first.isr);
11025         first.isr = NULL;
11026
11027         if (first.pr != NoProc) {
11028             ExitAnalyzeMode();
11029             DoSleep( appData.delayBeforeQuit );
11030             SendToProgram("quit\n", &first);
11031             DoSleep( appData.delayAfterQuit );
11032             DestroyChildProcess(first.pr, first.useSigterm);
11033             first.reload = TRUE;
11034         }
11035         first.pr = NoProc;
11036     }
11037     if (second.reuse) {
11038         /* Put second chess program into idle state */
11039         if (second.pr != NoProc &&
11040             gameMode == TwoMachinesPlay) {
11041             SendToProgram("force\n", &second);
11042             if (second.usePing) {
11043               char buf[MSG_SIZ];
11044               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11045               SendToProgram(buf, &second);
11046             }
11047         }
11048     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11049         /* Kill off second chess program */
11050         if (second.isr != NULL)
11051           RemoveInputSource(second.isr);
11052         second.isr = NULL;
11053
11054         if (second.pr != NoProc) {
11055             DoSleep( appData.delayBeforeQuit );
11056             SendToProgram("quit\n", &second);
11057             DoSleep( appData.delayAfterQuit );
11058             DestroyChildProcess(second.pr, second.useSigterm);
11059             second.reload = TRUE;
11060         }
11061         second.pr = NoProc;
11062     }
11063
11064     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11065         char resChar = '=';
11066         switch (result) {
11067         case WhiteWins:
11068           resChar = '+';
11069           if (first.twoMachinesColor[0] == 'w') {
11070             first.matchWins++;
11071           } else {
11072             second.matchWins++;
11073           }
11074           break;
11075         case BlackWins:
11076           resChar = '-';
11077           if (first.twoMachinesColor[0] == 'b') {
11078             first.matchWins++;
11079           } else {
11080             second.matchWins++;
11081           }
11082           break;
11083         case GameUnfinished:
11084           resChar = ' ';
11085         default:
11086           break;
11087         }
11088
11089         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11090         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11091             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11092             ReserveGame(nextGame, resChar); // sets nextGame
11093             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11094             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11095         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11096
11097         if (nextGame <= appData.matchGames && !abortMatch) {
11098             gameMode = nextGameMode;
11099             matchGame = nextGame; // this will be overruled in tourney mode!
11100             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11101             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11102             endingGame = 0; /* [HGM] crash */
11103             return;
11104         } else {
11105             gameMode = nextGameMode;
11106             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11107                      first.tidy, second.tidy,
11108                      first.matchWins, second.matchWins,
11109                      appData.matchGames - (first.matchWins + second.matchWins));
11110             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11111             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11112             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11113             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11114                 first.twoMachinesColor = "black\n";
11115                 second.twoMachinesColor = "white\n";
11116             } else {
11117                 first.twoMachinesColor = "white\n";
11118                 second.twoMachinesColor = "black\n";
11119             }
11120         }
11121     }
11122     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11123         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11124       ExitAnalyzeMode();
11125     gameMode = nextGameMode;
11126     ModeHighlight();
11127     endingGame = 0;  /* [HGM] crash */
11128     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11129         if(matchMode == TRUE) { // match through command line: exit with or without popup
11130             if(ranking) {
11131                 ToNrEvent(forwardMostMove);
11132                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11133                 else ExitEvent(0);
11134             } else DisplayFatalError(buf, 0, 0);
11135         } else { // match through menu; just stop, with or without popup
11136             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11137             ModeHighlight();
11138             if(ranking){
11139                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11140             } else DisplayNote(buf);
11141       }
11142       if(ranking) free(ranking);
11143     }
11144 }
11145
11146 /* Assumes program was just initialized (initString sent).
11147    Leaves program in force mode. */
11148 void
11149 FeedMovesToProgram (ChessProgramState *cps, int upto)
11150 {
11151     int i;
11152
11153     if (appData.debugMode)
11154       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11155               startedFromSetupPosition ? "position and " : "",
11156               backwardMostMove, upto, cps->which);
11157     if(currentlyInitializedVariant != gameInfo.variant) {
11158       char buf[MSG_SIZ];
11159         // [HGM] variantswitch: make engine aware of new variant
11160         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11161                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11162         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11163         SendToProgram(buf, cps);
11164         currentlyInitializedVariant = gameInfo.variant;
11165     }
11166     SendToProgram("force\n", cps);
11167     if (startedFromSetupPosition) {
11168         SendBoard(cps, backwardMostMove);
11169     if (appData.debugMode) {
11170         fprintf(debugFP, "feedMoves\n");
11171     }
11172     }
11173     for (i = backwardMostMove; i < upto; i++) {
11174         SendMoveToProgram(i, cps);
11175     }
11176 }
11177
11178
11179 int
11180 ResurrectChessProgram ()
11181 {
11182      /* The chess program may have exited.
11183         If so, restart it and feed it all the moves made so far. */
11184     static int doInit = 0;
11185
11186     if (appData.noChessProgram) return 1;
11187
11188     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11189         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11190         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11191         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11192     } else {
11193         if (first.pr != NoProc) return 1;
11194         StartChessProgram(&first);
11195     }
11196     InitChessProgram(&first, FALSE);
11197     FeedMovesToProgram(&first, currentMove);
11198
11199     if (!first.sendTime) {
11200         /* can't tell gnuchess what its clock should read,
11201            so we bow to its notion. */
11202         ResetClocks();
11203         timeRemaining[0][currentMove] = whiteTimeRemaining;
11204         timeRemaining[1][currentMove] = blackTimeRemaining;
11205     }
11206
11207     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11208                 appData.icsEngineAnalyze) && first.analysisSupport) {
11209       SendToProgram("analyze\n", &first);
11210       first.analyzing = TRUE;
11211     }
11212     return 1;
11213 }
11214
11215 /*
11216  * Button procedures
11217  */
11218 void
11219 Reset (int redraw, int init)
11220 {
11221     int i;
11222
11223     if (appData.debugMode) {
11224         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11225                 redraw, init, gameMode);
11226     }
11227     CleanupTail(); // [HGM] vari: delete any stored variations
11228     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11229     pausing = pauseExamInvalid = FALSE;
11230     startedFromSetupPosition = blackPlaysFirst = FALSE;
11231     firstMove = TRUE;
11232     whiteFlag = blackFlag = FALSE;
11233     userOfferedDraw = FALSE;
11234     hintRequested = bookRequested = FALSE;
11235     first.maybeThinking = FALSE;
11236     second.maybeThinking = FALSE;
11237     first.bookSuspend = FALSE; // [HGM] book
11238     second.bookSuspend = FALSE;
11239     thinkOutput[0] = NULLCHAR;
11240     lastHint[0] = NULLCHAR;
11241     ClearGameInfo(&gameInfo);
11242     gameInfo.variant = StringToVariant(appData.variant);
11243     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11244     ics_user_moved = ics_clock_paused = FALSE;
11245     ics_getting_history = H_FALSE;
11246     ics_gamenum = -1;
11247     white_holding[0] = black_holding[0] = NULLCHAR;
11248     ClearProgramStats();
11249     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11250
11251     ResetFrontEnd();
11252     ClearHighlights();
11253     flipView = appData.flipView;
11254     ClearPremoveHighlights();
11255     gotPremove = FALSE;
11256     alarmSounded = FALSE;
11257
11258     GameEnds(EndOfFile, NULL, GE_PLAYER);
11259     if(appData.serverMovesName != NULL) {
11260         /* [HGM] prepare to make moves file for broadcasting */
11261         clock_t t = clock();
11262         if(serverMoves != NULL) fclose(serverMoves);
11263         serverMoves = fopen(appData.serverMovesName, "r");
11264         if(serverMoves != NULL) {
11265             fclose(serverMoves);
11266             /* delay 15 sec before overwriting, so all clients can see end */
11267             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11268         }
11269         serverMoves = fopen(appData.serverMovesName, "w");
11270     }
11271
11272     ExitAnalyzeMode();
11273     gameMode = BeginningOfGame;
11274     ModeHighlight();
11275     if(appData.icsActive) gameInfo.variant = VariantNormal;
11276     currentMove = forwardMostMove = backwardMostMove = 0;
11277     MarkTargetSquares(1);
11278     InitPosition(redraw);
11279     for (i = 0; i < MAX_MOVES; i++) {
11280         if (commentList[i] != NULL) {
11281             free(commentList[i]);
11282             commentList[i] = NULL;
11283         }
11284     }
11285     ResetClocks();
11286     timeRemaining[0][0] = whiteTimeRemaining;
11287     timeRemaining[1][0] = blackTimeRemaining;
11288
11289     if (first.pr == NoProc) {
11290         StartChessProgram(&first);
11291     }
11292     if (init) {
11293             InitChessProgram(&first, startedFromSetupPosition);
11294     }
11295     DisplayTitle("");
11296     DisplayMessage("", "");
11297     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11298     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11299     ClearMap();        // [HGM] exclude: invalidate map
11300 }
11301
11302 void
11303 AutoPlayGameLoop ()
11304 {
11305     for (;;) {
11306         if (!AutoPlayOneMove())
11307           return;
11308         if (matchMode || appData.timeDelay == 0)
11309           continue;
11310         if (appData.timeDelay < 0)
11311           return;
11312         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11313         break;
11314     }
11315 }
11316
11317 void
11318 AnalyzeNextGame()
11319 {
11320     ReloadGame(1); // next game
11321 }
11322
11323 int
11324 AutoPlayOneMove ()
11325 {
11326     int fromX, fromY, toX, toY;
11327
11328     if (appData.debugMode) {
11329       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11330     }
11331
11332     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11333       return FALSE;
11334
11335     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11336       pvInfoList[currentMove].depth = programStats.depth;
11337       pvInfoList[currentMove].score = programStats.score;
11338       pvInfoList[currentMove].time  = 0;
11339       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11340       else { // append analysis of final position as comment
11341         char buf[MSG_SIZ];
11342         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11343         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11344       }
11345       programStats.depth = 0;
11346     }
11347
11348     if (currentMove >= forwardMostMove) {
11349       if(gameMode == AnalyzeFile) {
11350           if(appData.loadGameIndex == -1) {
11351             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11352           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11353           } else {
11354           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11355         }
11356       }
11357 //      gameMode = EndOfGame;
11358 //      ModeHighlight();
11359
11360       /* [AS] Clear current move marker at the end of a game */
11361       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11362
11363       return FALSE;
11364     }
11365
11366     toX = moveList[currentMove][2] - AAA;
11367     toY = moveList[currentMove][3] - ONE;
11368
11369     if (moveList[currentMove][1] == '@') {
11370         if (appData.highlightLastMove) {
11371             SetHighlights(-1, -1, toX, toY);
11372         }
11373     } else {
11374         fromX = moveList[currentMove][0] - AAA;
11375         fromY = moveList[currentMove][1] - ONE;
11376
11377         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11378
11379         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11380
11381         if (appData.highlightLastMove) {
11382             SetHighlights(fromX, fromY, toX, toY);
11383         }
11384     }
11385     DisplayMove(currentMove);
11386     SendMoveToProgram(currentMove++, &first);
11387     DisplayBothClocks();
11388     DrawPosition(FALSE, boards[currentMove]);
11389     // [HGM] PV info: always display, routine tests if empty
11390     DisplayComment(currentMove - 1, commentList[currentMove]);
11391     return TRUE;
11392 }
11393
11394
11395 int
11396 LoadGameOneMove (ChessMove readAhead)
11397 {
11398     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11399     char promoChar = NULLCHAR;
11400     ChessMove moveType;
11401     char move[MSG_SIZ];
11402     char *p, *q;
11403
11404     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11405         gameMode != AnalyzeMode && gameMode != Training) {
11406         gameFileFP = NULL;
11407         return FALSE;
11408     }
11409
11410     yyboardindex = forwardMostMove;
11411     if (readAhead != EndOfFile) {
11412       moveType = readAhead;
11413     } else {
11414       if (gameFileFP == NULL)
11415           return FALSE;
11416       moveType = (ChessMove) Myylex();
11417     }
11418
11419     done = FALSE;
11420     switch (moveType) {
11421       case Comment:
11422         if (appData.debugMode)
11423           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11424         p = yy_text;
11425
11426         /* append the comment but don't display it */
11427         AppendComment(currentMove, p, FALSE);
11428         return TRUE;
11429
11430       case WhiteCapturesEnPassant:
11431       case BlackCapturesEnPassant:
11432       case WhitePromotion:
11433       case BlackPromotion:
11434       case WhiteNonPromotion:
11435       case BlackNonPromotion:
11436       case NormalMove:
11437       case WhiteKingSideCastle:
11438       case WhiteQueenSideCastle:
11439       case BlackKingSideCastle:
11440       case BlackQueenSideCastle:
11441       case WhiteKingSideCastleWild:
11442       case WhiteQueenSideCastleWild:
11443       case BlackKingSideCastleWild:
11444       case BlackQueenSideCastleWild:
11445       /* PUSH Fabien */
11446       case WhiteHSideCastleFR:
11447       case WhiteASideCastleFR:
11448       case BlackHSideCastleFR:
11449       case BlackASideCastleFR:
11450       /* POP Fabien */
11451         if (appData.debugMode)
11452           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11453         fromX = currentMoveString[0] - AAA;
11454         fromY = currentMoveString[1] - ONE;
11455         toX = currentMoveString[2] - AAA;
11456         toY = currentMoveString[3] - ONE;
11457         promoChar = currentMoveString[4];
11458         break;
11459
11460       case WhiteDrop:
11461       case BlackDrop:
11462         if (appData.debugMode)
11463           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11464         fromX = moveType == WhiteDrop ?
11465           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11466         (int) CharToPiece(ToLower(currentMoveString[0]));
11467         fromY = DROP_RANK;
11468         toX = currentMoveString[2] - AAA;
11469         toY = currentMoveString[3] - ONE;
11470         break;
11471
11472       case WhiteWins:
11473       case BlackWins:
11474       case GameIsDrawn:
11475       case GameUnfinished:
11476         if (appData.debugMode)
11477           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11478         p = strchr(yy_text, '{');
11479         if (p == NULL) p = strchr(yy_text, '(');
11480         if (p == NULL) {
11481             p = yy_text;
11482             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11483         } else {
11484             q = strchr(p, *p == '{' ? '}' : ')');
11485             if (q != NULL) *q = NULLCHAR;
11486             p++;
11487         }
11488         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11489         GameEnds(moveType, p, GE_FILE);
11490         done = TRUE;
11491         if (cmailMsgLoaded) {
11492             ClearHighlights();
11493             flipView = WhiteOnMove(currentMove);
11494             if (moveType == GameUnfinished) flipView = !flipView;
11495             if (appData.debugMode)
11496               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11497         }
11498         break;
11499
11500       case EndOfFile:
11501         if (appData.debugMode)
11502           fprintf(debugFP, "Parser hit end of file\n");
11503         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11504           case MT_NONE:
11505           case MT_CHECK:
11506             break;
11507           case MT_CHECKMATE:
11508           case MT_STAINMATE:
11509             if (WhiteOnMove(currentMove)) {
11510                 GameEnds(BlackWins, "Black mates", GE_FILE);
11511             } else {
11512                 GameEnds(WhiteWins, "White mates", GE_FILE);
11513             }
11514             break;
11515           case MT_STALEMATE:
11516             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11517             break;
11518         }
11519         done = TRUE;
11520         break;
11521
11522       case MoveNumberOne:
11523         if (lastLoadGameStart == GNUChessGame) {
11524             /* GNUChessGames have numbers, but they aren't move numbers */
11525             if (appData.debugMode)
11526               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11527                       yy_text, (int) moveType);
11528             return LoadGameOneMove(EndOfFile); /* tail recursion */
11529         }
11530         /* else fall thru */
11531
11532       case XBoardGame:
11533       case GNUChessGame:
11534       case PGNTag:
11535         /* Reached start of next game in file */
11536         if (appData.debugMode)
11537           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11538         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11539           case MT_NONE:
11540           case MT_CHECK:
11541             break;
11542           case MT_CHECKMATE:
11543           case MT_STAINMATE:
11544             if (WhiteOnMove(currentMove)) {
11545                 GameEnds(BlackWins, "Black mates", GE_FILE);
11546             } else {
11547                 GameEnds(WhiteWins, "White mates", GE_FILE);
11548             }
11549             break;
11550           case MT_STALEMATE:
11551             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11552             break;
11553         }
11554         done = TRUE;
11555         break;
11556
11557       case PositionDiagram:     /* should not happen; ignore */
11558       case ElapsedTime:         /* ignore */
11559       case NAG:                 /* ignore */
11560         if (appData.debugMode)
11561           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11562                   yy_text, (int) moveType);
11563         return LoadGameOneMove(EndOfFile); /* tail recursion */
11564
11565       case IllegalMove:
11566         if (appData.testLegality) {
11567             if (appData.debugMode)
11568               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11569             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11570                     (forwardMostMove / 2) + 1,
11571                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11572             DisplayError(move, 0);
11573             done = TRUE;
11574         } else {
11575             if (appData.debugMode)
11576               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11577                       yy_text, currentMoveString);
11578             fromX = currentMoveString[0] - AAA;
11579             fromY = currentMoveString[1] - ONE;
11580             toX = currentMoveString[2] - AAA;
11581             toY = currentMoveString[3] - ONE;
11582             promoChar = currentMoveString[4];
11583         }
11584         break;
11585
11586       case AmbiguousMove:
11587         if (appData.debugMode)
11588           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11589         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11590                 (forwardMostMove / 2) + 1,
11591                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11592         DisplayError(move, 0);
11593         done = TRUE;
11594         break;
11595
11596       default:
11597       case ImpossibleMove:
11598         if (appData.debugMode)
11599           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11600         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11601                 (forwardMostMove / 2) + 1,
11602                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11603         DisplayError(move, 0);
11604         done = TRUE;
11605         break;
11606     }
11607
11608     if (done) {
11609         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11610             DrawPosition(FALSE, boards[currentMove]);
11611             DisplayBothClocks();
11612             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11613               DisplayComment(currentMove - 1, commentList[currentMove]);
11614         }
11615         (void) StopLoadGameTimer();
11616         gameFileFP = NULL;
11617         cmailOldMove = forwardMostMove;
11618         return FALSE;
11619     } else {
11620         /* currentMoveString is set as a side-effect of yylex */
11621
11622         thinkOutput[0] = NULLCHAR;
11623         MakeMove(fromX, fromY, toX, toY, promoChar);
11624         currentMove = forwardMostMove;
11625         return TRUE;
11626     }
11627 }
11628
11629 /* Load the nth game from the given file */
11630 int
11631 LoadGameFromFile (char *filename, int n, char *title, int useList)
11632 {
11633     FILE *f;
11634     char buf[MSG_SIZ];
11635
11636     if (strcmp(filename, "-") == 0) {
11637         f = stdin;
11638         title = "stdin";
11639     } else {
11640         f = fopen(filename, "rb");
11641         if (f == NULL) {
11642           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11643             DisplayError(buf, errno);
11644             return FALSE;
11645         }
11646     }
11647     if (fseek(f, 0, 0) == -1) {
11648         /* f is not seekable; probably a pipe */
11649         useList = FALSE;
11650     }
11651     if (useList && n == 0) {
11652         int error = GameListBuild(f);
11653         if (error) {
11654             DisplayError(_("Cannot build game list"), error);
11655         } else if (!ListEmpty(&gameList) &&
11656                    ((ListGame *) gameList.tailPred)->number > 1) {
11657             GameListPopUp(f, title);
11658             return TRUE;
11659         }
11660         GameListDestroy();
11661         n = 1;
11662     }
11663     if (n == 0) n = 1;
11664     return LoadGame(f, n, title, FALSE);
11665 }
11666
11667
11668 void
11669 MakeRegisteredMove ()
11670 {
11671     int fromX, fromY, toX, toY;
11672     char promoChar;
11673     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11674         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11675           case CMAIL_MOVE:
11676           case CMAIL_DRAW:
11677             if (appData.debugMode)
11678               fprintf(debugFP, "Restoring %s for game %d\n",
11679                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11680
11681             thinkOutput[0] = NULLCHAR;
11682             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11683             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11684             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11685             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11686             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11687             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11688             MakeMove(fromX, fromY, toX, toY, promoChar);
11689             ShowMove(fromX, fromY, toX, toY);
11690
11691             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11692               case MT_NONE:
11693               case MT_CHECK:
11694                 break;
11695
11696               case MT_CHECKMATE:
11697               case MT_STAINMATE:
11698                 if (WhiteOnMove(currentMove)) {
11699                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11700                 } else {
11701                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11702                 }
11703                 break;
11704
11705               case MT_STALEMATE:
11706                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11707                 break;
11708             }
11709
11710             break;
11711
11712           case CMAIL_RESIGN:
11713             if (WhiteOnMove(currentMove)) {
11714                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11715             } else {
11716                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11717             }
11718             break;
11719
11720           case CMAIL_ACCEPT:
11721             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11722             break;
11723
11724           default:
11725             break;
11726         }
11727     }
11728
11729     return;
11730 }
11731
11732 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11733 int
11734 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11735 {
11736     int retVal;
11737
11738     if (gameNumber > nCmailGames) {
11739         DisplayError(_("No more games in this message"), 0);
11740         return FALSE;
11741     }
11742     if (f == lastLoadGameFP) {
11743         int offset = gameNumber - lastLoadGameNumber;
11744         if (offset == 0) {
11745             cmailMsg[0] = NULLCHAR;
11746             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11747                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11748                 nCmailMovesRegistered--;
11749             }
11750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11751             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11752                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11753             }
11754         } else {
11755             if (! RegisterMove()) return FALSE;
11756         }
11757     }
11758
11759     retVal = LoadGame(f, gameNumber, title, useList);
11760
11761     /* Make move registered during previous look at this game, if any */
11762     MakeRegisteredMove();
11763
11764     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11765         commentList[currentMove]
11766           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11767         DisplayComment(currentMove - 1, commentList[currentMove]);
11768     }
11769
11770     return retVal;
11771 }
11772
11773 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11774 int
11775 ReloadGame (int offset)
11776 {
11777     int gameNumber = lastLoadGameNumber + offset;
11778     if (lastLoadGameFP == NULL) {
11779         DisplayError(_("No game has been loaded yet"), 0);
11780         return FALSE;
11781     }
11782     if (gameNumber <= 0) {
11783         DisplayError(_("Can't back up any further"), 0);
11784         return FALSE;
11785     }
11786     if (cmailMsgLoaded) {
11787         return CmailLoadGame(lastLoadGameFP, gameNumber,
11788                              lastLoadGameTitle, lastLoadGameUseList);
11789     } else {
11790         return LoadGame(lastLoadGameFP, gameNumber,
11791                         lastLoadGameTitle, lastLoadGameUseList);
11792     }
11793 }
11794
11795 int keys[EmptySquare+1];
11796
11797 int
11798 PositionMatches (Board b1, Board b2)
11799 {
11800     int r, f, sum=0;
11801     switch(appData.searchMode) {
11802         case 1: return CompareWithRights(b1, b2);
11803         case 2:
11804             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11805                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11806             }
11807             return TRUE;
11808         case 3:
11809             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11810               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11811                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11812             }
11813             return sum==0;
11814         case 4:
11815             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11816                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11817             }
11818             return sum==0;
11819     }
11820     return TRUE;
11821 }
11822
11823 #define Q_PROMO  4
11824 #define Q_EP     3
11825 #define Q_BCASTL 2
11826 #define Q_WCASTL 1
11827
11828 int pieceList[256], quickBoard[256];
11829 ChessSquare pieceType[256] = { EmptySquare };
11830 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11831 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11832 int soughtTotal, turn;
11833 Boolean epOK, flipSearch;
11834
11835 typedef struct {
11836     unsigned char piece, to;
11837 } Move;
11838
11839 #define DSIZE (250000)
11840
11841 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11842 Move *moveDatabase = initialSpace;
11843 unsigned int movePtr, dataSize = DSIZE;
11844
11845 int
11846 MakePieceList (Board board, int *counts)
11847 {
11848     int r, f, n=Q_PROMO, total=0;
11849     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11850     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11851         int sq = f + (r<<4);
11852         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11853             quickBoard[sq] = ++n;
11854             pieceList[n] = sq;
11855             pieceType[n] = board[r][f];
11856             counts[board[r][f]]++;
11857             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11858             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11859             total++;
11860         }
11861     }
11862     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11863     return total;
11864 }
11865
11866 void
11867 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11868 {
11869     int sq = fromX + (fromY<<4);
11870     int piece = quickBoard[sq];
11871     quickBoard[sq] = 0;
11872     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11873     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11874         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11875         moveDatabase[movePtr++].piece = Q_WCASTL;
11876         quickBoard[sq] = piece;
11877         piece = quickBoard[from]; quickBoard[from] = 0;
11878         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11879     } else
11880     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11881         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11882         moveDatabase[movePtr++].piece = Q_BCASTL;
11883         quickBoard[sq] = piece;
11884         piece = quickBoard[from]; quickBoard[from] = 0;
11885         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11886     } else
11887     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11888         quickBoard[(fromY<<4)+toX] = 0;
11889         moveDatabase[movePtr].piece = Q_EP;
11890         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11891         moveDatabase[movePtr].to = sq;
11892     } else
11893     if(promoPiece != pieceType[piece]) {
11894         moveDatabase[movePtr++].piece = Q_PROMO;
11895         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11896     }
11897     moveDatabase[movePtr].piece = piece;
11898     quickBoard[sq] = piece;
11899     movePtr++;
11900 }
11901
11902 int
11903 PackGame (Board board)
11904 {
11905     Move *newSpace = NULL;
11906     moveDatabase[movePtr].piece = 0; // terminate previous game
11907     if(movePtr > dataSize) {
11908         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11909         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11910         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11911         if(newSpace) {
11912             int i;
11913             Move *p = moveDatabase, *q = newSpace;
11914             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11915             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11916             moveDatabase = newSpace;
11917         } else { // calloc failed, we must be out of memory. Too bad...
11918             dataSize = 0; // prevent calloc events for all subsequent games
11919             return 0;     // and signal this one isn't cached
11920         }
11921     }
11922     movePtr++;
11923     MakePieceList(board, counts);
11924     return movePtr;
11925 }
11926
11927 int
11928 QuickCompare (Board board, int *minCounts, int *maxCounts)
11929 {   // compare according to search mode
11930     int r, f;
11931     switch(appData.searchMode)
11932     {
11933       case 1: // exact position match
11934         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11935         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11936             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11937         }
11938         break;
11939       case 2: // can have extra material on empty squares
11940         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11941             if(board[r][f] == EmptySquare) continue;
11942             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11943         }
11944         break;
11945       case 3: // material with exact Pawn structure
11946         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11947             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11948             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11949         } // fall through to material comparison
11950       case 4: // exact material
11951         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11952         break;
11953       case 6: // material range with given imbalance
11954         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11955         // fall through to range comparison
11956       case 5: // material range
11957         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11958     }
11959     return TRUE;
11960 }
11961
11962 int
11963 QuickScan (Board board, Move *move)
11964 {   // reconstruct game,and compare all positions in it
11965     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11966     do {
11967         int piece = move->piece;
11968         int to = move->to, from = pieceList[piece];
11969         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11970           if(!piece) return -1;
11971           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11972             piece = (++move)->piece;
11973             from = pieceList[piece];
11974             counts[pieceType[piece]]--;
11975             pieceType[piece] = (ChessSquare) move->to;
11976             counts[move->to]++;
11977           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11978             counts[pieceType[quickBoard[to]]]--;
11979             quickBoard[to] = 0; total--;
11980             move++;
11981             continue;
11982           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11983             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11984             from  = pieceList[piece]; // so this must be King
11985             quickBoard[from] = 0;
11986             pieceList[piece] = to;
11987             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11988             quickBoard[from] = 0; // rook
11989             quickBoard[to] = piece;
11990             to = move->to; piece = move->piece;
11991             goto aftercastle;
11992           }
11993         }
11994         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11995         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11996         quickBoard[from] = 0;
11997       aftercastle:
11998         quickBoard[to] = piece;
11999         pieceList[piece] = to;
12000         cnt++; turn ^= 3;
12001         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12002            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12003            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12004                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12005           ) {
12006             static int lastCounts[EmptySquare+1];
12007             int i;
12008             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12009             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12010         } else stretch = 0;
12011         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12012         move++;
12013     } while(1);
12014 }
12015
12016 void
12017 InitSearch ()
12018 {
12019     int r, f;
12020     flipSearch = FALSE;
12021     CopyBoard(soughtBoard, boards[currentMove]);
12022     soughtTotal = MakePieceList(soughtBoard, maxSought);
12023     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12024     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12025     CopyBoard(reverseBoard, boards[currentMove]);
12026     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12027         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12028         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12029         reverseBoard[r][f] = piece;
12030     }
12031     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12032     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12033     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12034                  || (boards[currentMove][CASTLING][2] == NoRights ||
12035                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12036                  && (boards[currentMove][CASTLING][5] == NoRights ||
12037                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12038       ) {
12039         flipSearch = TRUE;
12040         CopyBoard(flipBoard, soughtBoard);
12041         CopyBoard(rotateBoard, reverseBoard);
12042         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12043             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12044             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12045         }
12046     }
12047     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12048     if(appData.searchMode >= 5) {
12049         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12050         MakePieceList(soughtBoard, minSought);
12051         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12052     }
12053     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12054         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12055 }
12056
12057 GameInfo dummyInfo;
12058 static int creatingBook;
12059
12060 int
12061 GameContainsPosition (FILE *f, ListGame *lg)
12062 {
12063     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12064     int fromX, fromY, toX, toY;
12065     char promoChar;
12066     static int initDone=FALSE;
12067
12068     // weed out games based on numerical tag comparison
12069     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12070     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12071     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12072     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12073     if(!initDone) {
12074         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12075         initDone = TRUE;
12076     }
12077     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12078     else CopyBoard(boards[scratch], initialPosition); // default start position
12079     if(lg->moves) {
12080         turn = btm + 1;
12081         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12082         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12083     }
12084     if(btm) plyNr++;
12085     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12086     fseek(f, lg->offset, 0);
12087     yynewfile(f);
12088     while(1) {
12089         yyboardindex = scratch;
12090         quickFlag = plyNr+1;
12091         next = Myylex();
12092         quickFlag = 0;
12093         switch(next) {
12094             case PGNTag:
12095                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12096             default:
12097                 continue;
12098
12099             case XBoardGame:
12100             case GNUChessGame:
12101                 if(plyNr) return -1; // after we have seen moves, this is for new game
12102               continue;
12103
12104             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12105             case ImpossibleMove:
12106             case WhiteWins: // game ends here with these four
12107             case BlackWins:
12108             case GameIsDrawn:
12109             case GameUnfinished:
12110                 return -1;
12111
12112             case IllegalMove:
12113                 if(appData.testLegality) return -1;
12114             case WhiteCapturesEnPassant:
12115             case BlackCapturesEnPassant:
12116             case WhitePromotion:
12117             case BlackPromotion:
12118             case WhiteNonPromotion:
12119             case BlackNonPromotion:
12120             case NormalMove:
12121             case WhiteKingSideCastle:
12122             case WhiteQueenSideCastle:
12123             case BlackKingSideCastle:
12124             case BlackQueenSideCastle:
12125             case WhiteKingSideCastleWild:
12126             case WhiteQueenSideCastleWild:
12127             case BlackKingSideCastleWild:
12128             case BlackQueenSideCastleWild:
12129             case WhiteHSideCastleFR:
12130             case WhiteASideCastleFR:
12131             case BlackHSideCastleFR:
12132             case BlackASideCastleFR:
12133                 fromX = currentMoveString[0] - AAA;
12134                 fromY = currentMoveString[1] - ONE;
12135                 toX = currentMoveString[2] - AAA;
12136                 toY = currentMoveString[3] - ONE;
12137                 promoChar = currentMoveString[4];
12138                 break;
12139             case WhiteDrop:
12140             case BlackDrop:
12141                 fromX = next == WhiteDrop ?
12142                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12143                   (int) CharToPiece(ToLower(currentMoveString[0]));
12144                 fromY = DROP_RANK;
12145                 toX = currentMoveString[2] - AAA;
12146                 toY = currentMoveString[3] - ONE;
12147                 promoChar = 0;
12148                 break;
12149         }
12150         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12151         plyNr++;
12152         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12153         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12154         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12155         if(appData.findMirror) {
12156             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12157             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12158         }
12159     }
12160 }
12161
12162 /* Load the nth game from open file f */
12163 int
12164 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12165 {
12166     ChessMove cm;
12167     char buf[MSG_SIZ];
12168     int gn = gameNumber;
12169     ListGame *lg = NULL;
12170     int numPGNTags = 0;
12171     int err, pos = -1;
12172     GameMode oldGameMode;
12173     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12174
12175     if (appData.debugMode)
12176         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12177
12178     if (gameMode == Training )
12179         SetTrainingModeOff();
12180
12181     oldGameMode = gameMode;
12182     if (gameMode != BeginningOfGame) {
12183       Reset(FALSE, TRUE);
12184     }
12185
12186     gameFileFP = f;
12187     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12188         fclose(lastLoadGameFP);
12189     }
12190
12191     if (useList) {
12192         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12193
12194         if (lg) {
12195             fseek(f, lg->offset, 0);
12196             GameListHighlight(gameNumber);
12197             pos = lg->position;
12198             gn = 1;
12199         }
12200         else {
12201             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12202               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12203             else
12204             DisplayError(_("Game number out of range"), 0);
12205             return FALSE;
12206         }
12207     } else {
12208         GameListDestroy();
12209         if (fseek(f, 0, 0) == -1) {
12210             if (f == lastLoadGameFP ?
12211                 gameNumber == lastLoadGameNumber + 1 :
12212                 gameNumber == 1) {
12213                 gn = 1;
12214             } else {
12215                 DisplayError(_("Can't seek on game file"), 0);
12216                 return FALSE;
12217             }
12218         }
12219     }
12220     lastLoadGameFP = f;
12221     lastLoadGameNumber = gameNumber;
12222     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12223     lastLoadGameUseList = useList;
12224
12225     yynewfile(f);
12226
12227     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12228       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12229                 lg->gameInfo.black);
12230             DisplayTitle(buf);
12231     } else if (*title != NULLCHAR) {
12232         if (gameNumber > 1) {
12233           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12234             DisplayTitle(buf);
12235         } else {
12236             DisplayTitle(title);
12237         }
12238     }
12239
12240     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12241         gameMode = PlayFromGameFile;
12242         ModeHighlight();
12243     }
12244
12245     currentMove = forwardMostMove = backwardMostMove = 0;
12246     CopyBoard(boards[0], initialPosition);
12247     StopClocks();
12248
12249     /*
12250      * Skip the first gn-1 games in the file.
12251      * Also skip over anything that precedes an identifiable
12252      * start of game marker, to avoid being confused by
12253      * garbage at the start of the file.  Currently
12254      * recognized start of game markers are the move number "1",
12255      * the pattern "gnuchess .* game", the pattern
12256      * "^[#;%] [^ ]* game file", and a PGN tag block.
12257      * A game that starts with one of the latter two patterns
12258      * will also have a move number 1, possibly
12259      * following a position diagram.
12260      * 5-4-02: Let's try being more lenient and allowing a game to
12261      * start with an unnumbered move.  Does that break anything?
12262      */
12263     cm = lastLoadGameStart = EndOfFile;
12264     while (gn > 0) {
12265         yyboardindex = forwardMostMove;
12266         cm = (ChessMove) Myylex();
12267         switch (cm) {
12268           case EndOfFile:
12269             if (cmailMsgLoaded) {
12270                 nCmailGames = CMAIL_MAX_GAMES - gn;
12271             } else {
12272                 Reset(TRUE, TRUE);
12273                 DisplayError(_("Game not found in file"), 0);
12274             }
12275             return FALSE;
12276
12277           case GNUChessGame:
12278           case XBoardGame:
12279             gn--;
12280             lastLoadGameStart = cm;
12281             break;
12282
12283           case MoveNumberOne:
12284             switch (lastLoadGameStart) {
12285               case GNUChessGame:
12286               case XBoardGame:
12287               case PGNTag:
12288                 break;
12289               case MoveNumberOne:
12290               case EndOfFile:
12291                 gn--;           /* count this game */
12292                 lastLoadGameStart = cm;
12293                 break;
12294               default:
12295                 /* impossible */
12296                 break;
12297             }
12298             break;
12299
12300           case PGNTag:
12301             switch (lastLoadGameStart) {
12302               case GNUChessGame:
12303               case PGNTag:
12304               case MoveNumberOne:
12305               case EndOfFile:
12306                 gn--;           /* count this game */
12307                 lastLoadGameStart = cm;
12308                 break;
12309               case XBoardGame:
12310                 lastLoadGameStart = cm; /* game counted already */
12311                 break;
12312               default:
12313                 /* impossible */
12314                 break;
12315             }
12316             if (gn > 0) {
12317                 do {
12318                     yyboardindex = forwardMostMove;
12319                     cm = (ChessMove) Myylex();
12320                 } while (cm == PGNTag || cm == Comment);
12321             }
12322             break;
12323
12324           case WhiteWins:
12325           case BlackWins:
12326           case GameIsDrawn:
12327             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12328                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12329                     != CMAIL_OLD_RESULT) {
12330                     nCmailResults ++ ;
12331                     cmailResult[  CMAIL_MAX_GAMES
12332                                 - gn - 1] = CMAIL_OLD_RESULT;
12333                 }
12334             }
12335             break;
12336
12337           case NormalMove:
12338             /* Only a NormalMove can be at the start of a game
12339              * without a position diagram. */
12340             if (lastLoadGameStart == EndOfFile ) {
12341               gn--;
12342               lastLoadGameStart = MoveNumberOne;
12343             }
12344             break;
12345
12346           default:
12347             break;
12348         }
12349     }
12350
12351     if (appData.debugMode)
12352       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12353
12354     if (cm == XBoardGame) {
12355         /* Skip any header junk before position diagram and/or move 1 */
12356         for (;;) {
12357             yyboardindex = forwardMostMove;
12358             cm = (ChessMove) Myylex();
12359
12360             if (cm == EndOfFile ||
12361                 cm == GNUChessGame || cm == XBoardGame) {
12362                 /* Empty game; pretend end-of-file and handle later */
12363                 cm = EndOfFile;
12364                 break;
12365             }
12366
12367             if (cm == MoveNumberOne || cm == PositionDiagram ||
12368                 cm == PGNTag || cm == Comment)
12369               break;
12370         }
12371     } else if (cm == GNUChessGame) {
12372         if (gameInfo.event != NULL) {
12373             free(gameInfo.event);
12374         }
12375         gameInfo.event = StrSave(yy_text);
12376     }
12377
12378     startedFromSetupPosition = FALSE;
12379     while (cm == PGNTag) {
12380         if (appData.debugMode)
12381           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12382         err = ParsePGNTag(yy_text, &gameInfo);
12383         if (!err) numPGNTags++;
12384
12385         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12386         if(gameInfo.variant != oldVariant) {
12387             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12388             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12389             InitPosition(TRUE);
12390             oldVariant = gameInfo.variant;
12391             if (appData.debugMode)
12392               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12393         }
12394
12395
12396         if (gameInfo.fen != NULL) {
12397           Board initial_position;
12398           startedFromSetupPosition = TRUE;
12399           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12400             Reset(TRUE, TRUE);
12401             DisplayError(_("Bad FEN position in file"), 0);
12402             return FALSE;
12403           }
12404           CopyBoard(boards[0], initial_position);
12405           if (blackPlaysFirst) {
12406             currentMove = forwardMostMove = backwardMostMove = 1;
12407             CopyBoard(boards[1], initial_position);
12408             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12409             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12410             timeRemaining[0][1] = whiteTimeRemaining;
12411             timeRemaining[1][1] = blackTimeRemaining;
12412             if (commentList[0] != NULL) {
12413               commentList[1] = commentList[0];
12414               commentList[0] = NULL;
12415             }
12416           } else {
12417             currentMove = forwardMostMove = backwardMostMove = 0;
12418           }
12419           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12420           {   int i;
12421               initialRulePlies = FENrulePlies;
12422               for( i=0; i< nrCastlingRights; i++ )
12423                   initialRights[i] = initial_position[CASTLING][i];
12424           }
12425           yyboardindex = forwardMostMove;
12426           free(gameInfo.fen);
12427           gameInfo.fen = NULL;
12428         }
12429
12430         yyboardindex = forwardMostMove;
12431         cm = (ChessMove) Myylex();
12432
12433         /* Handle comments interspersed among the tags */
12434         while (cm == Comment) {
12435             char *p;
12436             if (appData.debugMode)
12437               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12438             p = yy_text;
12439             AppendComment(currentMove, p, FALSE);
12440             yyboardindex = forwardMostMove;
12441             cm = (ChessMove) Myylex();
12442         }
12443     }
12444
12445     /* don't rely on existence of Event tag since if game was
12446      * pasted from clipboard the Event tag may not exist
12447      */
12448     if (numPGNTags > 0){
12449         char *tags;
12450         if (gameInfo.variant == VariantNormal) {
12451           VariantClass v = StringToVariant(gameInfo.event);
12452           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12453           if(v < VariantShogi) gameInfo.variant = v;
12454         }
12455         if (!matchMode) {
12456           if( appData.autoDisplayTags ) {
12457             tags = PGNTags(&gameInfo);
12458             TagsPopUp(tags, CmailMsg());
12459             free(tags);
12460           }
12461         }
12462     } else {
12463         /* Make something up, but don't display it now */
12464         SetGameInfo();
12465         TagsPopDown();
12466     }
12467
12468     if (cm == PositionDiagram) {
12469         int i, j;
12470         char *p;
12471         Board initial_position;
12472
12473         if (appData.debugMode)
12474           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12475
12476         if (!startedFromSetupPosition) {
12477             p = yy_text;
12478             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12479               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12480                 switch (*p) {
12481                   case '{':
12482                   case '[':
12483                   case '-':
12484                   case ' ':
12485                   case '\t':
12486                   case '\n':
12487                   case '\r':
12488                     break;
12489                   default:
12490                     initial_position[i][j++] = CharToPiece(*p);
12491                     break;
12492                 }
12493             while (*p == ' ' || *p == '\t' ||
12494                    *p == '\n' || *p == '\r') p++;
12495
12496             if (strncmp(p, "black", strlen("black"))==0)
12497               blackPlaysFirst = TRUE;
12498             else
12499               blackPlaysFirst = FALSE;
12500             startedFromSetupPosition = TRUE;
12501
12502             CopyBoard(boards[0], initial_position);
12503             if (blackPlaysFirst) {
12504                 currentMove = forwardMostMove = backwardMostMove = 1;
12505                 CopyBoard(boards[1], initial_position);
12506                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12507                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12508                 timeRemaining[0][1] = whiteTimeRemaining;
12509                 timeRemaining[1][1] = blackTimeRemaining;
12510                 if (commentList[0] != NULL) {
12511                     commentList[1] = commentList[0];
12512                     commentList[0] = NULL;
12513                 }
12514             } else {
12515                 currentMove = forwardMostMove = backwardMostMove = 0;
12516             }
12517         }
12518         yyboardindex = forwardMostMove;
12519         cm = (ChessMove) Myylex();
12520     }
12521
12522   if(!creatingBook) {
12523     if (first.pr == NoProc) {
12524         StartChessProgram(&first);
12525     }
12526     InitChessProgram(&first, FALSE);
12527     SendToProgram("force\n", &first);
12528     if (startedFromSetupPosition) {
12529         SendBoard(&first, forwardMostMove);
12530     if (appData.debugMode) {
12531         fprintf(debugFP, "Load Game\n");
12532     }
12533         DisplayBothClocks();
12534     }
12535   }
12536
12537     /* [HGM] server: flag to write setup moves in broadcast file as one */
12538     loadFlag = appData.suppressLoadMoves;
12539
12540     while (cm == Comment) {
12541         char *p;
12542         if (appData.debugMode)
12543           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12544         p = yy_text;
12545         AppendComment(currentMove, p, FALSE);
12546         yyboardindex = forwardMostMove;
12547         cm = (ChessMove) Myylex();
12548     }
12549
12550     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12551         cm == WhiteWins || cm == BlackWins ||
12552         cm == GameIsDrawn || cm == GameUnfinished) {
12553         DisplayMessage("", _("No moves in game"));
12554         if (cmailMsgLoaded) {
12555             if (appData.debugMode)
12556               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12557             ClearHighlights();
12558             flipView = FALSE;
12559         }
12560         DrawPosition(FALSE, boards[currentMove]);
12561         DisplayBothClocks();
12562         gameMode = EditGame;
12563         ModeHighlight();
12564         gameFileFP = NULL;
12565         cmailOldMove = 0;
12566         return TRUE;
12567     }
12568
12569     // [HGM] PV info: routine tests if comment empty
12570     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12571         DisplayComment(currentMove - 1, commentList[currentMove]);
12572     }
12573     if (!matchMode && appData.timeDelay != 0)
12574       DrawPosition(FALSE, boards[currentMove]);
12575
12576     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12577       programStats.ok_to_send = 1;
12578     }
12579
12580     /* if the first token after the PGN tags is a move
12581      * and not move number 1, retrieve it from the parser
12582      */
12583     if (cm != MoveNumberOne)
12584         LoadGameOneMove(cm);
12585
12586     /* load the remaining moves from the file */
12587     while (LoadGameOneMove(EndOfFile)) {
12588       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12589       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12590     }
12591
12592     /* rewind to the start of the game */
12593     currentMove = backwardMostMove;
12594
12595     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12596
12597     if (oldGameMode == AnalyzeFile) {
12598       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12599       AnalyzeFileEvent();
12600     } else
12601     if (oldGameMode == AnalyzeMode) {
12602       AnalyzeFileEvent();
12603     }
12604
12605     if(creatingBook) return TRUE;
12606     if (!matchMode && pos > 0) {
12607         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12608     } else
12609     if (matchMode || appData.timeDelay == 0) {
12610       ToEndEvent();
12611     } else if (appData.timeDelay > 0) {
12612       AutoPlayGameLoop();
12613     }
12614
12615     if (appData.debugMode)
12616         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12617
12618     loadFlag = 0; /* [HGM] true game starts */
12619     return TRUE;
12620 }
12621
12622 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12623 int
12624 ReloadPosition (int offset)
12625 {
12626     int positionNumber = lastLoadPositionNumber + offset;
12627     if (lastLoadPositionFP == NULL) {
12628         DisplayError(_("No position has been loaded yet"), 0);
12629         return FALSE;
12630     }
12631     if (positionNumber <= 0) {
12632         DisplayError(_("Can't back up any further"), 0);
12633         return FALSE;
12634     }
12635     return LoadPosition(lastLoadPositionFP, positionNumber,
12636                         lastLoadPositionTitle);
12637 }
12638
12639 /* Load the nth position from the given file */
12640 int
12641 LoadPositionFromFile (char *filename, int n, char *title)
12642 {
12643     FILE *f;
12644     char buf[MSG_SIZ];
12645
12646     if (strcmp(filename, "-") == 0) {
12647         return LoadPosition(stdin, n, "stdin");
12648     } else {
12649         f = fopen(filename, "rb");
12650         if (f == NULL) {
12651             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12652             DisplayError(buf, errno);
12653             return FALSE;
12654         } else {
12655             return LoadPosition(f, n, title);
12656         }
12657     }
12658 }
12659
12660 /* Load the nth position from the given open file, and close it */
12661 int
12662 LoadPosition (FILE *f, int positionNumber, char *title)
12663 {
12664     char *p, line[MSG_SIZ];
12665     Board initial_position;
12666     int i, j, fenMode, pn;
12667
12668     if (gameMode == Training )
12669         SetTrainingModeOff();
12670
12671     if (gameMode != BeginningOfGame) {
12672         Reset(FALSE, TRUE);
12673     }
12674     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12675         fclose(lastLoadPositionFP);
12676     }
12677     if (positionNumber == 0) positionNumber = 1;
12678     lastLoadPositionFP = f;
12679     lastLoadPositionNumber = positionNumber;
12680     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12681     if (first.pr == NoProc && !appData.noChessProgram) {
12682       StartChessProgram(&first);
12683       InitChessProgram(&first, FALSE);
12684     }
12685     pn = positionNumber;
12686     if (positionNumber < 0) {
12687         /* Negative position number means to seek to that byte offset */
12688         if (fseek(f, -positionNumber, 0) == -1) {
12689             DisplayError(_("Can't seek on position file"), 0);
12690             return FALSE;
12691         };
12692         pn = 1;
12693     } else {
12694         if (fseek(f, 0, 0) == -1) {
12695             if (f == lastLoadPositionFP ?
12696                 positionNumber == lastLoadPositionNumber + 1 :
12697                 positionNumber == 1) {
12698                 pn = 1;
12699             } else {
12700                 DisplayError(_("Can't seek on position file"), 0);
12701                 return FALSE;
12702             }
12703         }
12704     }
12705     /* See if this file is FEN or old-style xboard */
12706     if (fgets(line, MSG_SIZ, f) == NULL) {
12707         DisplayError(_("Position not found in file"), 0);
12708         return FALSE;
12709     }
12710     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12711     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12712
12713     if (pn >= 2) {
12714         if (fenMode || line[0] == '#') pn--;
12715         while (pn > 0) {
12716             /* skip positions before number pn */
12717             if (fgets(line, MSG_SIZ, f) == NULL) {
12718                 Reset(TRUE, TRUE);
12719                 DisplayError(_("Position not found in file"), 0);
12720                 return FALSE;
12721             }
12722             if (fenMode || line[0] == '#') pn--;
12723         }
12724     }
12725
12726     if (fenMode) {
12727         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12728             DisplayError(_("Bad FEN position in file"), 0);
12729             return FALSE;
12730         }
12731     } else {
12732         (void) fgets(line, MSG_SIZ, f);
12733         (void) fgets(line, MSG_SIZ, f);
12734
12735         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12736             (void) fgets(line, MSG_SIZ, f);
12737             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12738                 if (*p == ' ')
12739                   continue;
12740                 initial_position[i][j++] = CharToPiece(*p);
12741             }
12742         }
12743
12744         blackPlaysFirst = FALSE;
12745         if (!feof(f)) {
12746             (void) fgets(line, MSG_SIZ, f);
12747             if (strncmp(line, "black", strlen("black"))==0)
12748               blackPlaysFirst = TRUE;
12749         }
12750     }
12751     startedFromSetupPosition = TRUE;
12752
12753     CopyBoard(boards[0], initial_position);
12754     if (blackPlaysFirst) {
12755         currentMove = forwardMostMove = backwardMostMove = 1;
12756         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12757         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12758         CopyBoard(boards[1], initial_position);
12759         DisplayMessage("", _("Black to play"));
12760     } else {
12761         currentMove = forwardMostMove = backwardMostMove = 0;
12762         DisplayMessage("", _("White to play"));
12763     }
12764     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12765     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12766         SendToProgram("force\n", &first);
12767         SendBoard(&first, forwardMostMove);
12768     }
12769     if (appData.debugMode) {
12770 int i, j;
12771   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12772   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12773         fprintf(debugFP, "Load Position\n");
12774     }
12775
12776     if (positionNumber > 1) {
12777       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12778         DisplayTitle(line);
12779     } else {
12780         DisplayTitle(title);
12781     }
12782     gameMode = EditGame;
12783     ModeHighlight();
12784     ResetClocks();
12785     timeRemaining[0][1] = whiteTimeRemaining;
12786     timeRemaining[1][1] = blackTimeRemaining;
12787     DrawPosition(FALSE, boards[currentMove]);
12788
12789     return TRUE;
12790 }
12791
12792
12793 void
12794 CopyPlayerNameIntoFileName (char **dest, char *src)
12795 {
12796     while (*src != NULLCHAR && *src != ',') {
12797         if (*src == ' ') {
12798             *(*dest)++ = '_';
12799             src++;
12800         } else {
12801             *(*dest)++ = *src++;
12802         }
12803     }
12804 }
12805
12806 char *
12807 DefaultFileName (char *ext)
12808 {
12809     static char def[MSG_SIZ];
12810     char *p;
12811
12812     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12813         p = def;
12814         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12815         *p++ = '-';
12816         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12817         *p++ = '.';
12818         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12819     } else {
12820         def[0] = NULLCHAR;
12821     }
12822     return def;
12823 }
12824
12825 /* Save the current game to the given file */
12826 int
12827 SaveGameToFile (char *filename, int append)
12828 {
12829     FILE *f;
12830     char buf[MSG_SIZ];
12831     int result, i, t,tot=0;
12832
12833     if (strcmp(filename, "-") == 0) {
12834         return SaveGame(stdout, 0, NULL);
12835     } else {
12836         for(i=0; i<10; i++) { // upto 10 tries
12837              f = fopen(filename, append ? "a" : "w");
12838              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12839              if(f || errno != 13) break;
12840              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12841              tot += t;
12842         }
12843         if (f == NULL) {
12844             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12845             DisplayError(buf, errno);
12846             return FALSE;
12847         } else {
12848             safeStrCpy(buf, lastMsg, MSG_SIZ);
12849             DisplayMessage(_("Waiting for access to save file"), "");
12850             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12851             DisplayMessage(_("Saving game"), "");
12852             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12853             result = SaveGame(f, 0, NULL);
12854             DisplayMessage(buf, "");
12855             return result;
12856         }
12857     }
12858 }
12859
12860 char *
12861 SavePart (char *str)
12862 {
12863     static char buf[MSG_SIZ];
12864     char *p;
12865
12866     p = strchr(str, ' ');
12867     if (p == NULL) return str;
12868     strncpy(buf, str, p - str);
12869     buf[p - str] = NULLCHAR;
12870     return buf;
12871 }
12872
12873 #define PGN_MAX_LINE 75
12874
12875 #define PGN_SIDE_WHITE  0
12876 #define PGN_SIDE_BLACK  1
12877
12878 static int
12879 FindFirstMoveOutOfBook (int side)
12880 {
12881     int result = -1;
12882
12883     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12884         int index = backwardMostMove;
12885         int has_book_hit = 0;
12886
12887         if( (index % 2) != side ) {
12888             index++;
12889         }
12890
12891         while( index < forwardMostMove ) {
12892             /* Check to see if engine is in book */
12893             int depth = pvInfoList[index].depth;
12894             int score = pvInfoList[index].score;
12895             int in_book = 0;
12896
12897             if( depth <= 2 ) {
12898                 in_book = 1;
12899             }
12900             else if( score == 0 && depth == 63 ) {
12901                 in_book = 1; /* Zappa */
12902             }
12903             else if( score == 2 && depth == 99 ) {
12904                 in_book = 1; /* Abrok */
12905             }
12906
12907             has_book_hit += in_book;
12908
12909             if( ! in_book ) {
12910                 result = index;
12911
12912                 break;
12913             }
12914
12915             index += 2;
12916         }
12917     }
12918
12919     return result;
12920 }
12921
12922 void
12923 GetOutOfBookInfo (char * buf)
12924 {
12925     int oob[2];
12926     int i;
12927     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12928
12929     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12930     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12931
12932     *buf = '\0';
12933
12934     if( oob[0] >= 0 || oob[1] >= 0 ) {
12935         for( i=0; i<2; i++ ) {
12936             int idx = oob[i];
12937
12938             if( idx >= 0 ) {
12939                 if( i > 0 && oob[0] >= 0 ) {
12940                     strcat( buf, "   " );
12941                 }
12942
12943                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12944                 sprintf( buf+strlen(buf), "%s%.2f",
12945                     pvInfoList[idx].score >= 0 ? "+" : "",
12946                     pvInfoList[idx].score / 100.0 );
12947             }
12948         }
12949     }
12950 }
12951
12952 /* Save game in PGN style and close the file */
12953 int
12954 SaveGamePGN (FILE *f)
12955 {
12956     int i, offset, linelen, newblock;
12957 //    char *movetext;
12958     char numtext[32];
12959     int movelen, numlen, blank;
12960     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12961
12962     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12963
12964     PrintPGNTags(f, &gameInfo);
12965
12966     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12967
12968     if (backwardMostMove > 0 || startedFromSetupPosition) {
12969         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12970         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12971         fprintf(f, "\n{--------------\n");
12972         PrintPosition(f, backwardMostMove);
12973         fprintf(f, "--------------}\n");
12974         free(fen);
12975     }
12976     else {
12977         /* [AS] Out of book annotation */
12978         if( appData.saveOutOfBookInfo ) {
12979             char buf[64];
12980
12981             GetOutOfBookInfo( buf );
12982
12983             if( buf[0] != '\0' ) {
12984                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12985             }
12986         }
12987
12988         fprintf(f, "\n");
12989     }
12990
12991     i = backwardMostMove;
12992     linelen = 0;
12993     newblock = TRUE;
12994
12995     while (i < forwardMostMove) {
12996         /* Print comments preceding this move */
12997         if (commentList[i] != NULL) {
12998             if (linelen > 0) fprintf(f, "\n");
12999             fprintf(f, "%s", commentList[i]);
13000             linelen = 0;
13001             newblock = TRUE;
13002         }
13003
13004         /* Format move number */
13005         if ((i % 2) == 0)
13006           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13007         else
13008           if (newblock)
13009             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13010           else
13011             numtext[0] = NULLCHAR;
13012
13013         numlen = strlen(numtext);
13014         newblock = FALSE;
13015
13016         /* Print move number */
13017         blank = linelen > 0 && numlen > 0;
13018         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13019             fprintf(f, "\n");
13020             linelen = 0;
13021             blank = 0;
13022         }
13023         if (blank) {
13024             fprintf(f, " ");
13025             linelen++;
13026         }
13027         fprintf(f, "%s", numtext);
13028         linelen += numlen;
13029
13030         /* Get move */
13031         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13032         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13033
13034         /* Print move */
13035         blank = linelen > 0 && movelen > 0;
13036         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13037             fprintf(f, "\n");
13038             linelen = 0;
13039             blank = 0;
13040         }
13041         if (blank) {
13042             fprintf(f, " ");
13043             linelen++;
13044         }
13045         fprintf(f, "%s", move_buffer);
13046         linelen += movelen;
13047
13048         /* [AS] Add PV info if present */
13049         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13050             /* [HGM] add time */
13051             char buf[MSG_SIZ]; int seconds;
13052
13053             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13054
13055             if( seconds <= 0)
13056               buf[0] = 0;
13057             else
13058               if( seconds < 30 )
13059                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13060               else
13061                 {
13062                   seconds = (seconds + 4)/10; // round to full seconds
13063                   if( seconds < 60 )
13064                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13065                   else
13066                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13067                 }
13068
13069             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13070                       pvInfoList[i].score >= 0 ? "+" : "",
13071                       pvInfoList[i].score / 100.0,
13072                       pvInfoList[i].depth,
13073                       buf );
13074
13075             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13076
13077             /* Print score/depth */
13078             blank = linelen > 0 && movelen > 0;
13079             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13080                 fprintf(f, "\n");
13081                 linelen = 0;
13082                 blank = 0;
13083             }
13084             if (blank) {
13085                 fprintf(f, " ");
13086                 linelen++;
13087             }
13088             fprintf(f, "%s", move_buffer);
13089             linelen += movelen;
13090         }
13091
13092         i++;
13093     }
13094
13095     /* Start a new line */
13096     if (linelen > 0) fprintf(f, "\n");
13097
13098     /* Print comments after last move */
13099     if (commentList[i] != NULL) {
13100         fprintf(f, "%s\n", commentList[i]);
13101     }
13102
13103     /* Print result */
13104     if (gameInfo.resultDetails != NULL &&
13105         gameInfo.resultDetails[0] != NULLCHAR) {
13106         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13107                 PGNResult(gameInfo.result));
13108     } else {
13109         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13110     }
13111
13112     fclose(f);
13113     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13114     return TRUE;
13115 }
13116
13117 /* Save game in old style and close the file */
13118 int
13119 SaveGameOldStyle (FILE *f)
13120 {
13121     int i, offset;
13122     time_t tm;
13123
13124     tm = time((time_t *) NULL);
13125
13126     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13127     PrintOpponents(f);
13128
13129     if (backwardMostMove > 0 || startedFromSetupPosition) {
13130         fprintf(f, "\n[--------------\n");
13131         PrintPosition(f, backwardMostMove);
13132         fprintf(f, "--------------]\n");
13133     } else {
13134         fprintf(f, "\n");
13135     }
13136
13137     i = backwardMostMove;
13138     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13139
13140     while (i < forwardMostMove) {
13141         if (commentList[i] != NULL) {
13142             fprintf(f, "[%s]\n", commentList[i]);
13143         }
13144
13145         if ((i % 2) == 1) {
13146             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13147             i++;
13148         } else {
13149             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13150             i++;
13151             if (commentList[i] != NULL) {
13152                 fprintf(f, "\n");
13153                 continue;
13154             }
13155             if (i >= forwardMostMove) {
13156                 fprintf(f, "\n");
13157                 break;
13158             }
13159             fprintf(f, "%s\n", parseList[i]);
13160             i++;
13161         }
13162     }
13163
13164     if (commentList[i] != NULL) {
13165         fprintf(f, "[%s]\n", commentList[i]);
13166     }
13167
13168     /* This isn't really the old style, but it's close enough */
13169     if (gameInfo.resultDetails != NULL &&
13170         gameInfo.resultDetails[0] != NULLCHAR) {
13171         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13172                 gameInfo.resultDetails);
13173     } else {
13174         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13175     }
13176
13177     fclose(f);
13178     return TRUE;
13179 }
13180
13181 /* Save the current game to open file f and close the file */
13182 int
13183 SaveGame (FILE *f, int dummy, char *dummy2)
13184 {
13185     if (gameMode == EditPosition) EditPositionDone(TRUE);
13186     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13187     if (appData.oldSaveStyle)
13188       return SaveGameOldStyle(f);
13189     else
13190       return SaveGamePGN(f);
13191 }
13192
13193 /* Save the current position to the given file */
13194 int
13195 SavePositionToFile (char *filename)
13196 {
13197     FILE *f;
13198     char buf[MSG_SIZ];
13199
13200     if (strcmp(filename, "-") == 0) {
13201         return SavePosition(stdout, 0, NULL);
13202     } else {
13203         f = fopen(filename, "a");
13204         if (f == NULL) {
13205             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13206             DisplayError(buf, errno);
13207             return FALSE;
13208         } else {
13209             safeStrCpy(buf, lastMsg, MSG_SIZ);
13210             DisplayMessage(_("Waiting for access to save file"), "");
13211             flock(fileno(f), LOCK_EX); // [HGM] lock
13212             DisplayMessage(_("Saving position"), "");
13213             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13214             SavePosition(f, 0, NULL);
13215             DisplayMessage(buf, "");
13216             return TRUE;
13217         }
13218     }
13219 }
13220
13221 /* Save the current position to the given open file and close the file */
13222 int
13223 SavePosition (FILE *f, int dummy, char *dummy2)
13224 {
13225     time_t tm;
13226     char *fen;
13227
13228     if (gameMode == EditPosition) EditPositionDone(TRUE);
13229     if (appData.oldSaveStyle) {
13230         tm = time((time_t *) NULL);
13231
13232         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13233         PrintOpponents(f);
13234         fprintf(f, "[--------------\n");
13235         PrintPosition(f, currentMove);
13236         fprintf(f, "--------------]\n");
13237     } else {
13238         fen = PositionToFEN(currentMove, NULL, 1);
13239         fprintf(f, "%s\n", fen);
13240         free(fen);
13241     }
13242     fclose(f);
13243     return TRUE;
13244 }
13245
13246 void
13247 ReloadCmailMsgEvent (int unregister)
13248 {
13249 #if !WIN32
13250     static char *inFilename = NULL;
13251     static char *outFilename;
13252     int i;
13253     struct stat inbuf, outbuf;
13254     int status;
13255
13256     /* Any registered moves are unregistered if unregister is set, */
13257     /* i.e. invoked by the signal handler */
13258     if (unregister) {
13259         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13260             cmailMoveRegistered[i] = FALSE;
13261             if (cmailCommentList[i] != NULL) {
13262                 free(cmailCommentList[i]);
13263                 cmailCommentList[i] = NULL;
13264             }
13265         }
13266         nCmailMovesRegistered = 0;
13267     }
13268
13269     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13270         cmailResult[i] = CMAIL_NOT_RESULT;
13271     }
13272     nCmailResults = 0;
13273
13274     if (inFilename == NULL) {
13275         /* Because the filenames are static they only get malloced once  */
13276         /* and they never get freed                                      */
13277         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13278         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13279
13280         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13281         sprintf(outFilename, "%s.out", appData.cmailGameName);
13282     }
13283
13284     status = stat(outFilename, &outbuf);
13285     if (status < 0) {
13286         cmailMailedMove = FALSE;
13287     } else {
13288         status = stat(inFilename, &inbuf);
13289         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13290     }
13291
13292     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13293        counts the games, notes how each one terminated, etc.
13294
13295        It would be nice to remove this kludge and instead gather all
13296        the information while building the game list.  (And to keep it
13297        in the game list nodes instead of having a bunch of fixed-size
13298        parallel arrays.)  Note this will require getting each game's
13299        termination from the PGN tags, as the game list builder does
13300        not process the game moves.  --mann
13301        */
13302     cmailMsgLoaded = TRUE;
13303     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13304
13305     /* Load first game in the file or popup game menu */
13306     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13307
13308 #endif /* !WIN32 */
13309     return;
13310 }
13311
13312 int
13313 RegisterMove ()
13314 {
13315     FILE *f;
13316     char string[MSG_SIZ];
13317
13318     if (   cmailMailedMove
13319         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13320         return TRUE;            /* Allow free viewing  */
13321     }
13322
13323     /* Unregister move to ensure that we don't leave RegisterMove        */
13324     /* with the move registered when the conditions for registering no   */
13325     /* longer hold                                                       */
13326     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13327         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13328         nCmailMovesRegistered --;
13329
13330         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13331           {
13332               free(cmailCommentList[lastLoadGameNumber - 1]);
13333               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13334           }
13335     }
13336
13337     if (cmailOldMove == -1) {
13338         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13339         return FALSE;
13340     }
13341
13342     if (currentMove > cmailOldMove + 1) {
13343         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13344         return FALSE;
13345     }
13346
13347     if (currentMove < cmailOldMove) {
13348         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13349         return FALSE;
13350     }
13351
13352     if (forwardMostMove > currentMove) {
13353         /* Silently truncate extra moves */
13354         TruncateGame();
13355     }
13356
13357     if (   (currentMove == cmailOldMove + 1)
13358         || (   (currentMove == cmailOldMove)
13359             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13360                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13361         if (gameInfo.result != GameUnfinished) {
13362             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13363         }
13364
13365         if (commentList[currentMove] != NULL) {
13366             cmailCommentList[lastLoadGameNumber - 1]
13367               = StrSave(commentList[currentMove]);
13368         }
13369         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13370
13371         if (appData.debugMode)
13372           fprintf(debugFP, "Saving %s for game %d\n",
13373                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13374
13375         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13376
13377         f = fopen(string, "w");
13378         if (appData.oldSaveStyle) {
13379             SaveGameOldStyle(f); /* also closes the file */
13380
13381             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13382             f = fopen(string, "w");
13383             SavePosition(f, 0, NULL); /* also closes the file */
13384         } else {
13385             fprintf(f, "{--------------\n");
13386             PrintPosition(f, currentMove);
13387             fprintf(f, "--------------}\n\n");
13388
13389             SaveGame(f, 0, NULL); /* also closes the file*/
13390         }
13391
13392         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13393         nCmailMovesRegistered ++;
13394     } else if (nCmailGames == 1) {
13395         DisplayError(_("You have not made a move yet"), 0);
13396         return FALSE;
13397     }
13398
13399     return TRUE;
13400 }
13401
13402 void
13403 MailMoveEvent ()
13404 {
13405 #if !WIN32
13406     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13407     FILE *commandOutput;
13408     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13409     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13410     int nBuffers;
13411     int i;
13412     int archived;
13413     char *arcDir;
13414
13415     if (! cmailMsgLoaded) {
13416         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13417         return;
13418     }
13419
13420     if (nCmailGames == nCmailResults) {
13421         DisplayError(_("No unfinished games"), 0);
13422         return;
13423     }
13424
13425 #if CMAIL_PROHIBIT_REMAIL
13426     if (cmailMailedMove) {
13427       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);
13428         DisplayError(msg, 0);
13429         return;
13430     }
13431 #endif
13432
13433     if (! (cmailMailedMove || RegisterMove())) return;
13434
13435     if (   cmailMailedMove
13436         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13437       snprintf(string, MSG_SIZ, partCommandString,
13438                appData.debugMode ? " -v" : "", appData.cmailGameName);
13439         commandOutput = popen(string, "r");
13440
13441         if (commandOutput == NULL) {
13442             DisplayError(_("Failed to invoke cmail"), 0);
13443         } else {
13444             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13445                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13446             }
13447             if (nBuffers > 1) {
13448                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13449                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13450                 nBytes = MSG_SIZ - 1;
13451             } else {
13452                 (void) memcpy(msg, buffer, nBytes);
13453             }
13454             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13455
13456             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13457                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13458
13459                 archived = TRUE;
13460                 for (i = 0; i < nCmailGames; i ++) {
13461                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13462                         archived = FALSE;
13463                     }
13464                 }
13465                 if (   archived
13466                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13467                         != NULL)) {
13468                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13469                            arcDir,
13470                            appData.cmailGameName,
13471                            gameInfo.date);
13472                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13473                     cmailMsgLoaded = FALSE;
13474                 }
13475             }
13476
13477             DisplayInformation(msg);
13478             pclose(commandOutput);
13479         }
13480     } else {
13481         if ((*cmailMsg) != '\0') {
13482             DisplayInformation(cmailMsg);
13483         }
13484     }
13485
13486     return;
13487 #endif /* !WIN32 */
13488 }
13489
13490 char *
13491 CmailMsg ()
13492 {
13493 #if WIN32
13494     return NULL;
13495 #else
13496     int  prependComma = 0;
13497     char number[5];
13498     char string[MSG_SIZ];       /* Space for game-list */
13499     int  i;
13500
13501     if (!cmailMsgLoaded) return "";
13502
13503     if (cmailMailedMove) {
13504       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13505     } else {
13506         /* Create a list of games left */
13507       snprintf(string, MSG_SIZ, "[");
13508         for (i = 0; i < nCmailGames; i ++) {
13509             if (! (   cmailMoveRegistered[i]
13510                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13511                 if (prependComma) {
13512                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13513                 } else {
13514                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13515                     prependComma = 1;
13516                 }
13517
13518                 strcat(string, number);
13519             }
13520         }
13521         strcat(string, "]");
13522
13523         if (nCmailMovesRegistered + nCmailResults == 0) {
13524             switch (nCmailGames) {
13525               case 1:
13526                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13527                 break;
13528
13529               case 2:
13530                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13531                 break;
13532
13533               default:
13534                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13535                          nCmailGames);
13536                 break;
13537             }
13538         } else {
13539             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13540               case 1:
13541                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13542                          string);
13543                 break;
13544
13545               case 0:
13546                 if (nCmailResults == nCmailGames) {
13547                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13548                 } else {
13549                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13550                 }
13551                 break;
13552
13553               default:
13554                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13555                          string);
13556             }
13557         }
13558     }
13559     return cmailMsg;
13560 #endif /* WIN32 */
13561 }
13562
13563 void
13564 ResetGameEvent ()
13565 {
13566     if (gameMode == Training)
13567       SetTrainingModeOff();
13568
13569     Reset(TRUE, TRUE);
13570     cmailMsgLoaded = FALSE;
13571     if (appData.icsActive) {
13572       SendToICS(ics_prefix);
13573       SendToICS("refresh\n");
13574     }
13575 }
13576
13577 void
13578 ExitEvent (int status)
13579 {
13580     exiting++;
13581     if (exiting > 2) {
13582       /* Give up on clean exit */
13583       exit(status);
13584     }
13585     if (exiting > 1) {
13586       /* Keep trying for clean exit */
13587       return;
13588     }
13589
13590     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13591
13592     if (telnetISR != NULL) {
13593       RemoveInputSource(telnetISR);
13594     }
13595     if (icsPR != NoProc) {
13596       DestroyChildProcess(icsPR, TRUE);
13597     }
13598
13599     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13600     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13601
13602     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13603     /* make sure this other one finishes before killing it!                  */
13604     if(endingGame) { int count = 0;
13605         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13606         while(endingGame && count++ < 10) DoSleep(1);
13607         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13608     }
13609
13610     /* Kill off chess programs */
13611     if (first.pr != NoProc) {
13612         ExitAnalyzeMode();
13613
13614         DoSleep( appData.delayBeforeQuit );
13615         SendToProgram("quit\n", &first);
13616         DoSleep( appData.delayAfterQuit );
13617         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13618     }
13619     if (second.pr != NoProc) {
13620         DoSleep( appData.delayBeforeQuit );
13621         SendToProgram("quit\n", &second);
13622         DoSleep( appData.delayAfterQuit );
13623         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13624     }
13625     if (first.isr != NULL) {
13626         RemoveInputSource(first.isr);
13627     }
13628     if (second.isr != NULL) {
13629         RemoveInputSource(second.isr);
13630     }
13631
13632     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13633     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13634
13635     ShutDownFrontEnd();
13636     exit(status);
13637 }
13638
13639 void
13640 PauseEngine (ChessProgramState *cps)
13641 {
13642     SendToProgram("pause\n", cps);
13643     cps->pause = 2;
13644 }
13645
13646 void
13647 UnPauseEngine (ChessProgramState *cps)
13648 {
13649     SendToProgram("resume\n", cps);
13650     cps->pause = 1;
13651 }
13652
13653 void
13654 PauseEvent ()
13655 {
13656     if (appData.debugMode)
13657         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13658     if (pausing) {
13659         pausing = FALSE;
13660         ModeHighlight();
13661         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13662             StartClocks();
13663             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13664                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13665                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13666             }
13667             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13668             HandleMachineMove(stashedInputMove, stalledEngine);
13669             stalledEngine = NULL;
13670             return;
13671         }
13672         if (gameMode == MachinePlaysWhite ||
13673             gameMode == TwoMachinesPlay   ||
13674             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13675             if(first.pause)  UnPauseEngine(&first);
13676             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13677             if(second.pause) UnPauseEngine(&second);
13678             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13679             StartClocks();
13680         } else {
13681             DisplayBothClocks();
13682         }
13683         if (gameMode == PlayFromGameFile) {
13684             if (appData.timeDelay >= 0)
13685                 AutoPlayGameLoop();
13686         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13687             Reset(FALSE, TRUE);
13688             SendToICS(ics_prefix);
13689             SendToICS("refresh\n");
13690         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13691             ForwardInner(forwardMostMove);
13692         }
13693         pauseExamInvalid = FALSE;
13694     } else {
13695         switch (gameMode) {
13696           default:
13697             return;
13698           case IcsExamining:
13699             pauseExamForwardMostMove = forwardMostMove;
13700             pauseExamInvalid = FALSE;
13701             /* fall through */
13702           case IcsObserving:
13703           case IcsPlayingWhite:
13704           case IcsPlayingBlack:
13705             pausing = TRUE;
13706             ModeHighlight();
13707             return;
13708           case PlayFromGameFile:
13709             (void) StopLoadGameTimer();
13710             pausing = TRUE;
13711             ModeHighlight();
13712             break;
13713           case BeginningOfGame:
13714             if (appData.icsActive) return;
13715             /* else fall through */
13716           case MachinePlaysWhite:
13717           case MachinePlaysBlack:
13718           case TwoMachinesPlay:
13719             if (forwardMostMove == 0)
13720               return;           /* don't pause if no one has moved */
13721             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13722                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13723                 if(onMove->pause) {           // thinking engine can be paused
13724                     PauseEngine(onMove);      // do it
13725                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13726                         PauseEngine(onMove->other);
13727                     else
13728                         SendToProgram("easy\n", onMove->other);
13729                     StopClocks();
13730                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13731             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13732                 if(first.pause) {
13733                     PauseEngine(&first);
13734                     StopClocks();
13735                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13736             } else { // human on move, pause pondering by either method
13737                 if(first.pause)
13738                     PauseEngine(&first);
13739                 else if(appData.ponderNextMove)
13740                     SendToProgram("easy\n", &first);
13741                 StopClocks();
13742             }
13743             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13744           case AnalyzeMode:
13745             pausing = TRUE;
13746             ModeHighlight();
13747             break;
13748         }
13749     }
13750 }
13751
13752 void
13753 EditCommentEvent ()
13754 {
13755     char title[MSG_SIZ];
13756
13757     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13758       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13759     } else {
13760       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13761                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13762                parseList[currentMove - 1]);
13763     }
13764
13765     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13766 }
13767
13768
13769 void
13770 EditTagsEvent ()
13771 {
13772     char *tags = PGNTags(&gameInfo);
13773     bookUp = FALSE;
13774     EditTagsPopUp(tags, NULL);
13775     free(tags);
13776 }
13777
13778 void
13779 ToggleSecond ()
13780 {
13781   if(second.analyzing) {
13782     SendToProgram("exit\n", &second);
13783     second.analyzing = FALSE;
13784   } else {
13785     if (second.pr == NoProc) StartChessProgram(&second);
13786     InitChessProgram(&second, FALSE);
13787     FeedMovesToProgram(&second, currentMove);
13788
13789     SendToProgram("analyze\n", &second);
13790     second.analyzing = TRUE;
13791   }
13792 }
13793
13794 /* Toggle ShowThinking */
13795 void
13796 ToggleShowThinking()
13797 {
13798   appData.showThinking = !appData.showThinking;
13799   ShowThinkingEvent();
13800 }
13801
13802 int
13803 AnalyzeModeEvent ()
13804 {
13805     char buf[MSG_SIZ];
13806
13807     if (!first.analysisSupport) {
13808       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13809       DisplayError(buf, 0);
13810       return 0;
13811     }
13812     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13813     if (appData.icsActive) {
13814         if (gameMode != IcsObserving) {
13815           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13816             DisplayError(buf, 0);
13817             /* secure check */
13818             if (appData.icsEngineAnalyze) {
13819                 if (appData.debugMode)
13820                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13821                 ExitAnalyzeMode();
13822                 ModeHighlight();
13823             }
13824             return 0;
13825         }
13826         /* if enable, user wants to disable icsEngineAnalyze */
13827         if (appData.icsEngineAnalyze) {
13828                 ExitAnalyzeMode();
13829                 ModeHighlight();
13830                 return 0;
13831         }
13832         appData.icsEngineAnalyze = TRUE;
13833         if (appData.debugMode)
13834             fprintf(debugFP, "ICS engine analyze starting... \n");
13835     }
13836
13837     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13838     if (appData.noChessProgram || gameMode == AnalyzeMode)
13839       return 0;
13840
13841     if (gameMode != AnalyzeFile) {
13842         if (!appData.icsEngineAnalyze) {
13843                EditGameEvent();
13844                if (gameMode != EditGame) return 0;
13845         }
13846         if (!appData.showThinking) ToggleShowThinking();
13847         ResurrectChessProgram();
13848         SendToProgram("analyze\n", &first);
13849         first.analyzing = TRUE;
13850         /*first.maybeThinking = TRUE;*/
13851         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13852         EngineOutputPopUp();
13853     }
13854     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13855     pausing = FALSE;
13856     ModeHighlight();
13857     SetGameInfo();
13858
13859     StartAnalysisClock();
13860     GetTimeMark(&lastNodeCountTime);
13861     lastNodeCount = 0;
13862     return 1;
13863 }
13864
13865 void
13866 AnalyzeFileEvent ()
13867 {
13868     if (appData.noChessProgram || gameMode == AnalyzeFile)
13869       return;
13870
13871     if (!first.analysisSupport) {
13872       char buf[MSG_SIZ];
13873       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13874       DisplayError(buf, 0);
13875       return;
13876     }
13877
13878     if (gameMode != AnalyzeMode) {
13879         keepInfo = 1; // mere annotating should not alter PGN tags
13880         EditGameEvent();
13881         keepInfo = 0;
13882         if (gameMode != EditGame) return;
13883         if (!appData.showThinking) ToggleShowThinking();
13884         ResurrectChessProgram();
13885         SendToProgram("analyze\n", &first);
13886         first.analyzing = TRUE;
13887         /*first.maybeThinking = TRUE;*/
13888         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13889         EngineOutputPopUp();
13890     }
13891     gameMode = AnalyzeFile;
13892     pausing = FALSE;
13893     ModeHighlight();
13894
13895     StartAnalysisClock();
13896     GetTimeMark(&lastNodeCountTime);
13897     lastNodeCount = 0;
13898     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13899     AnalysisPeriodicEvent(1);
13900 }
13901
13902 void
13903 MachineWhiteEvent ()
13904 {
13905     char buf[MSG_SIZ];
13906     char *bookHit = NULL;
13907
13908     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13909       return;
13910
13911
13912     if (gameMode == PlayFromGameFile ||
13913         gameMode == TwoMachinesPlay  ||
13914         gameMode == Training         ||
13915         gameMode == AnalyzeMode      ||
13916         gameMode == EndOfGame)
13917         EditGameEvent();
13918
13919     if (gameMode == EditPosition)
13920         EditPositionDone(TRUE);
13921
13922     if (!WhiteOnMove(currentMove)) {
13923         DisplayError(_("It is not White's turn"), 0);
13924         return;
13925     }
13926
13927     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13928       ExitAnalyzeMode();
13929
13930     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13931         gameMode == AnalyzeFile)
13932         TruncateGame();
13933
13934     ResurrectChessProgram();    /* in case it isn't running */
13935     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13936         gameMode = MachinePlaysWhite;
13937         ResetClocks();
13938     } else
13939     gameMode = MachinePlaysWhite;
13940     pausing = FALSE;
13941     ModeHighlight();
13942     SetGameInfo();
13943     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13944     DisplayTitle(buf);
13945     if (first.sendName) {
13946       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13947       SendToProgram(buf, &first);
13948     }
13949     if (first.sendTime) {
13950       if (first.useColors) {
13951         SendToProgram("black\n", &first); /*gnu kludge*/
13952       }
13953       SendTimeRemaining(&first, TRUE);
13954     }
13955     if (first.useColors) {
13956       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13957     }
13958     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13959     SetMachineThinkingEnables();
13960     first.maybeThinking = TRUE;
13961     StartClocks();
13962     firstMove = FALSE;
13963
13964     if (appData.autoFlipView && !flipView) {
13965       flipView = !flipView;
13966       DrawPosition(FALSE, NULL);
13967       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13968     }
13969
13970     if(bookHit) { // [HGM] book: simulate book reply
13971         static char bookMove[MSG_SIZ]; // a bit generous?
13972
13973         programStats.nodes = programStats.depth = programStats.time =
13974         programStats.score = programStats.got_only_move = 0;
13975         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13976
13977         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13978         strcat(bookMove, bookHit);
13979         HandleMachineMove(bookMove, &first);
13980     }
13981 }
13982
13983 void
13984 MachineBlackEvent ()
13985 {
13986   char buf[MSG_SIZ];
13987   char *bookHit = NULL;
13988
13989     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13990         return;
13991
13992
13993     if (gameMode == PlayFromGameFile ||
13994         gameMode == TwoMachinesPlay  ||
13995         gameMode == Training         ||
13996         gameMode == AnalyzeMode      ||
13997         gameMode == EndOfGame)
13998         EditGameEvent();
13999
14000     if (gameMode == EditPosition)
14001         EditPositionDone(TRUE);
14002
14003     if (WhiteOnMove(currentMove)) {
14004         DisplayError(_("It is not Black's turn"), 0);
14005         return;
14006     }
14007
14008     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14009       ExitAnalyzeMode();
14010
14011     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14012         gameMode == AnalyzeFile)
14013         TruncateGame();
14014
14015     ResurrectChessProgram();    /* in case it isn't running */
14016     gameMode = MachinePlaysBlack;
14017     pausing = FALSE;
14018     ModeHighlight();
14019     SetGameInfo();
14020     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14021     DisplayTitle(buf);
14022     if (first.sendName) {
14023       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14024       SendToProgram(buf, &first);
14025     }
14026     if (first.sendTime) {
14027       if (first.useColors) {
14028         SendToProgram("white\n", &first); /*gnu kludge*/
14029       }
14030       SendTimeRemaining(&first, FALSE);
14031     }
14032     if (first.useColors) {
14033       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14034     }
14035     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14036     SetMachineThinkingEnables();
14037     first.maybeThinking = TRUE;
14038     StartClocks();
14039
14040     if (appData.autoFlipView && flipView) {
14041       flipView = !flipView;
14042       DrawPosition(FALSE, NULL);
14043       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14044     }
14045     if(bookHit) { // [HGM] book: simulate book reply
14046         static char bookMove[MSG_SIZ]; // a bit generous?
14047
14048         programStats.nodes = programStats.depth = programStats.time =
14049         programStats.score = programStats.got_only_move = 0;
14050         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14051
14052         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14053         strcat(bookMove, bookHit);
14054         HandleMachineMove(bookMove, &first);
14055     }
14056 }
14057
14058
14059 void
14060 DisplayTwoMachinesTitle ()
14061 {
14062     char buf[MSG_SIZ];
14063     if (appData.matchGames > 0) {
14064         if(appData.tourneyFile[0]) {
14065           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14066                    gameInfo.white, _("vs."), gameInfo.black,
14067                    nextGame+1, appData.matchGames+1,
14068                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14069         } else
14070         if (first.twoMachinesColor[0] == 'w') {
14071           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14072                    gameInfo.white, _("vs."),  gameInfo.black,
14073                    first.matchWins, second.matchWins,
14074                    matchGame - 1 - (first.matchWins + second.matchWins));
14075         } else {
14076           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14077                    gameInfo.white, _("vs."), gameInfo.black,
14078                    second.matchWins, first.matchWins,
14079                    matchGame - 1 - (first.matchWins + second.matchWins));
14080         }
14081     } else {
14082       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14083     }
14084     DisplayTitle(buf);
14085 }
14086
14087 void
14088 SettingsMenuIfReady ()
14089 {
14090   if (second.lastPing != second.lastPong) {
14091     DisplayMessage("", _("Waiting for second chess program"));
14092     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14093     return;
14094   }
14095   ThawUI();
14096   DisplayMessage("", "");
14097   SettingsPopUp(&second);
14098 }
14099
14100 int
14101 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14102 {
14103     char buf[MSG_SIZ];
14104     if (cps->pr == NoProc) {
14105         StartChessProgram(cps);
14106         if (cps->protocolVersion == 1) {
14107           retry();
14108           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14109         } else {
14110           /* kludge: allow timeout for initial "feature" command */
14111           if(retry != TwoMachinesEventIfReady) FreezeUI();
14112           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14113           DisplayMessage("", buf);
14114           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14115         }
14116         return 1;
14117     }
14118     return 0;
14119 }
14120
14121 void
14122 TwoMachinesEvent P((void))
14123 {
14124     int i;
14125     char buf[MSG_SIZ];
14126     ChessProgramState *onmove;
14127     char *bookHit = NULL;
14128     static int stalling = 0;
14129     TimeMark now;
14130     long wait;
14131
14132     if (appData.noChessProgram) return;
14133
14134     switch (gameMode) {
14135       case TwoMachinesPlay:
14136         return;
14137       case MachinePlaysWhite:
14138       case MachinePlaysBlack:
14139         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14140             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14141             return;
14142         }
14143         /* fall through */
14144       case BeginningOfGame:
14145       case PlayFromGameFile:
14146       case EndOfGame:
14147         EditGameEvent();
14148         if (gameMode != EditGame) return;
14149         break;
14150       case EditPosition:
14151         EditPositionDone(TRUE);
14152         break;
14153       case AnalyzeMode:
14154       case AnalyzeFile:
14155         ExitAnalyzeMode();
14156         break;
14157       case EditGame:
14158       default:
14159         break;
14160     }
14161
14162 //    forwardMostMove = currentMove;
14163     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14164     startingEngine = TRUE;
14165
14166     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14167
14168     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14169     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14170       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14171       return;
14172     }
14173     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14174
14175     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14176         startingEngine = FALSE;
14177         DisplayError("second engine does not play this", 0);
14178         return;
14179     }
14180
14181     if(!stalling) {
14182       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14183       SendToProgram("force\n", &second);
14184       stalling = 1;
14185       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14186       return;
14187     }
14188     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14189     if(appData.matchPause>10000 || appData.matchPause<10)
14190                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14191     wait = SubtractTimeMarks(&now, &pauseStart);
14192     if(wait < appData.matchPause) {
14193         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14194         return;
14195     }
14196     // we are now committed to starting the game
14197     stalling = 0;
14198     DisplayMessage("", "");
14199     if (startedFromSetupPosition) {
14200         SendBoard(&second, backwardMostMove);
14201     if (appData.debugMode) {
14202         fprintf(debugFP, "Two Machines\n");
14203     }
14204     }
14205     for (i = backwardMostMove; i < forwardMostMove; i++) {
14206         SendMoveToProgram(i, &second);
14207     }
14208
14209     gameMode = TwoMachinesPlay;
14210     pausing = startingEngine = FALSE;
14211     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14212     SetGameInfo();
14213     DisplayTwoMachinesTitle();
14214     firstMove = TRUE;
14215     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14216         onmove = &first;
14217     } else {
14218         onmove = &second;
14219     }
14220     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14221     SendToProgram(first.computerString, &first);
14222     if (first.sendName) {
14223       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14224       SendToProgram(buf, &first);
14225     }
14226     SendToProgram(second.computerString, &second);
14227     if (second.sendName) {
14228       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14229       SendToProgram(buf, &second);
14230     }
14231
14232     ResetClocks();
14233     if (!first.sendTime || !second.sendTime) {
14234         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14235         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14236     }
14237     if (onmove->sendTime) {
14238       if (onmove->useColors) {
14239         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14240       }
14241       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14242     }
14243     if (onmove->useColors) {
14244       SendToProgram(onmove->twoMachinesColor, onmove);
14245     }
14246     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14247 //    SendToProgram("go\n", onmove);
14248     onmove->maybeThinking = TRUE;
14249     SetMachineThinkingEnables();
14250
14251     StartClocks();
14252
14253     if(bookHit) { // [HGM] book: simulate book reply
14254         static char bookMove[MSG_SIZ]; // a bit generous?
14255
14256         programStats.nodes = programStats.depth = programStats.time =
14257         programStats.score = programStats.got_only_move = 0;
14258         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14259
14260         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14261         strcat(bookMove, bookHit);
14262         savedMessage = bookMove; // args for deferred call
14263         savedState = onmove;
14264         ScheduleDelayedEvent(DeferredBookMove, 1);
14265     }
14266 }
14267
14268 void
14269 TrainingEvent ()
14270 {
14271     if (gameMode == Training) {
14272       SetTrainingModeOff();
14273       gameMode = PlayFromGameFile;
14274       DisplayMessage("", _("Training mode off"));
14275     } else {
14276       gameMode = Training;
14277       animateTraining = appData.animate;
14278
14279       /* make sure we are not already at the end of the game */
14280       if (currentMove < forwardMostMove) {
14281         SetTrainingModeOn();
14282         DisplayMessage("", _("Training mode on"));
14283       } else {
14284         gameMode = PlayFromGameFile;
14285         DisplayError(_("Already at end of game"), 0);
14286       }
14287     }
14288     ModeHighlight();
14289 }
14290
14291 void
14292 IcsClientEvent ()
14293 {
14294     if (!appData.icsActive) return;
14295     switch (gameMode) {
14296       case IcsPlayingWhite:
14297       case IcsPlayingBlack:
14298       case IcsObserving:
14299       case IcsIdle:
14300       case BeginningOfGame:
14301       case IcsExamining:
14302         return;
14303
14304       case EditGame:
14305         break;
14306
14307       case EditPosition:
14308         EditPositionDone(TRUE);
14309         break;
14310
14311       case AnalyzeMode:
14312       case AnalyzeFile:
14313         ExitAnalyzeMode();
14314         break;
14315
14316       default:
14317         EditGameEvent();
14318         break;
14319     }
14320
14321     gameMode = IcsIdle;
14322     ModeHighlight();
14323     return;
14324 }
14325
14326 void
14327 EditGameEvent ()
14328 {
14329     int i;
14330
14331     switch (gameMode) {
14332       case Training:
14333         SetTrainingModeOff();
14334         break;
14335       case MachinePlaysWhite:
14336       case MachinePlaysBlack:
14337       case BeginningOfGame:
14338         SendToProgram("force\n", &first);
14339         SetUserThinkingEnables();
14340         break;
14341       case PlayFromGameFile:
14342         (void) StopLoadGameTimer();
14343         if (gameFileFP != NULL) {
14344             gameFileFP = NULL;
14345         }
14346         break;
14347       case EditPosition:
14348         EditPositionDone(TRUE);
14349         break;
14350       case AnalyzeMode:
14351       case AnalyzeFile:
14352         ExitAnalyzeMode();
14353         SendToProgram("force\n", &first);
14354         break;
14355       case TwoMachinesPlay:
14356         GameEnds(EndOfFile, NULL, GE_PLAYER);
14357         ResurrectChessProgram();
14358         SetUserThinkingEnables();
14359         break;
14360       case EndOfGame:
14361         ResurrectChessProgram();
14362         break;
14363       case IcsPlayingBlack:
14364       case IcsPlayingWhite:
14365         DisplayError(_("Warning: You are still playing a game"), 0);
14366         break;
14367       case IcsObserving:
14368         DisplayError(_("Warning: You are still observing a game"), 0);
14369         break;
14370       case IcsExamining:
14371         DisplayError(_("Warning: You are still examining a game"), 0);
14372         break;
14373       case IcsIdle:
14374         break;
14375       case EditGame:
14376       default:
14377         return;
14378     }
14379
14380     pausing = FALSE;
14381     StopClocks();
14382     first.offeredDraw = second.offeredDraw = 0;
14383
14384     if (gameMode == PlayFromGameFile) {
14385         whiteTimeRemaining = timeRemaining[0][currentMove];
14386         blackTimeRemaining = timeRemaining[1][currentMove];
14387         DisplayTitle("");
14388     }
14389
14390     if (gameMode == MachinePlaysWhite ||
14391         gameMode == MachinePlaysBlack ||
14392         gameMode == TwoMachinesPlay ||
14393         gameMode == EndOfGame) {
14394         i = forwardMostMove;
14395         while (i > currentMove) {
14396             SendToProgram("undo\n", &first);
14397             i--;
14398         }
14399         if(!adjustedClock) {
14400         whiteTimeRemaining = timeRemaining[0][currentMove];
14401         blackTimeRemaining = timeRemaining[1][currentMove];
14402         DisplayBothClocks();
14403         }
14404         if (whiteFlag || blackFlag) {
14405             whiteFlag = blackFlag = 0;
14406         }
14407         DisplayTitle("");
14408     }
14409
14410     gameMode = EditGame;
14411     ModeHighlight();
14412     SetGameInfo();
14413 }
14414
14415
14416 void
14417 EditPositionEvent ()
14418 {
14419     if (gameMode == EditPosition) {
14420         EditGameEvent();
14421         return;
14422     }
14423
14424     EditGameEvent();
14425     if (gameMode != EditGame) return;
14426
14427     gameMode = EditPosition;
14428     ModeHighlight();
14429     SetGameInfo();
14430     if (currentMove > 0)
14431       CopyBoard(boards[0], boards[currentMove]);
14432
14433     blackPlaysFirst = !WhiteOnMove(currentMove);
14434     ResetClocks();
14435     currentMove = forwardMostMove = backwardMostMove = 0;
14436     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14437     DisplayMove(-1);
14438     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14439 }
14440
14441 void
14442 ExitAnalyzeMode ()
14443 {
14444     /* [DM] icsEngineAnalyze - possible call from other functions */
14445     if (appData.icsEngineAnalyze) {
14446         appData.icsEngineAnalyze = FALSE;
14447
14448         DisplayMessage("",_("Close ICS engine analyze..."));
14449     }
14450     if (first.analysisSupport && first.analyzing) {
14451       SendToBoth("exit\n");
14452       first.analyzing = second.analyzing = FALSE;
14453     }
14454     thinkOutput[0] = NULLCHAR;
14455 }
14456
14457 void
14458 EditPositionDone (Boolean fakeRights)
14459 {
14460     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14461
14462     startedFromSetupPosition = TRUE;
14463     InitChessProgram(&first, FALSE);
14464     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14465       boards[0][EP_STATUS] = EP_NONE;
14466       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14467       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14468         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14469         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14470       } else boards[0][CASTLING][2] = NoRights;
14471       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14472         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14473         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14474       } else boards[0][CASTLING][5] = NoRights;
14475       if(gameInfo.variant == VariantSChess) {
14476         int i;
14477         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14478           boards[0][VIRGIN][i] = 0;
14479           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14480           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14481         }
14482       }
14483     }
14484     SendToProgram("force\n", &first);
14485     if (blackPlaysFirst) {
14486         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14487         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14488         currentMove = forwardMostMove = backwardMostMove = 1;
14489         CopyBoard(boards[1], boards[0]);
14490     } else {
14491         currentMove = forwardMostMove = backwardMostMove = 0;
14492     }
14493     SendBoard(&first, forwardMostMove);
14494     if (appData.debugMode) {
14495         fprintf(debugFP, "EditPosDone\n");
14496     }
14497     DisplayTitle("");
14498     DisplayMessage("", "");
14499     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14500     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14501     gameMode = EditGame;
14502     ModeHighlight();
14503     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14504     ClearHighlights(); /* [AS] */
14505 }
14506
14507 /* Pause for `ms' milliseconds */
14508 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14509 void
14510 TimeDelay (long ms)
14511 {
14512     TimeMark m1, m2;
14513
14514     GetTimeMark(&m1);
14515     do {
14516         GetTimeMark(&m2);
14517     } while (SubtractTimeMarks(&m2, &m1) < ms);
14518 }
14519
14520 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14521 void
14522 SendMultiLineToICS (char *buf)
14523 {
14524     char temp[MSG_SIZ+1], *p;
14525     int len;
14526
14527     len = strlen(buf);
14528     if (len > MSG_SIZ)
14529       len = MSG_SIZ;
14530
14531     strncpy(temp, buf, len);
14532     temp[len] = 0;
14533
14534     p = temp;
14535     while (*p) {
14536         if (*p == '\n' || *p == '\r')
14537           *p = ' ';
14538         ++p;
14539     }
14540
14541     strcat(temp, "\n");
14542     SendToICS(temp);
14543     SendToPlayer(temp, strlen(temp));
14544 }
14545
14546 void
14547 SetWhiteToPlayEvent ()
14548 {
14549     if (gameMode == EditPosition) {
14550         blackPlaysFirst = FALSE;
14551         DisplayBothClocks();    /* works because currentMove is 0 */
14552     } else if (gameMode == IcsExamining) {
14553         SendToICS(ics_prefix);
14554         SendToICS("tomove white\n");
14555     }
14556 }
14557
14558 void
14559 SetBlackToPlayEvent ()
14560 {
14561     if (gameMode == EditPosition) {
14562         blackPlaysFirst = TRUE;
14563         currentMove = 1;        /* kludge */
14564         DisplayBothClocks();
14565         currentMove = 0;
14566     } else if (gameMode == IcsExamining) {
14567         SendToICS(ics_prefix);
14568         SendToICS("tomove black\n");
14569     }
14570 }
14571
14572 void
14573 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14574 {
14575     char buf[MSG_SIZ];
14576     ChessSquare piece = boards[0][y][x];
14577
14578     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14579
14580     switch (selection) {
14581       case ClearBoard:
14582         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14583             SendToICS(ics_prefix);
14584             SendToICS("bsetup clear\n");
14585         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14586             SendToICS(ics_prefix);
14587             SendToICS("clearboard\n");
14588         } else {
14589             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14590                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14591                 for (y = 0; y < BOARD_HEIGHT; y++) {
14592                     if (gameMode == IcsExamining) {
14593                         if (boards[currentMove][y][x] != EmptySquare) {
14594                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14595                                     AAA + x, ONE + y);
14596                             SendToICS(buf);
14597                         }
14598                     } else {
14599                         boards[0][y][x] = p;
14600                     }
14601                 }
14602             }
14603         }
14604         if (gameMode == EditPosition) {
14605             DrawPosition(FALSE, boards[0]);
14606         }
14607         break;
14608
14609       case WhitePlay:
14610         SetWhiteToPlayEvent();
14611         break;
14612
14613       case BlackPlay:
14614         SetBlackToPlayEvent();
14615         break;
14616
14617       case EmptySquare:
14618         if (gameMode == IcsExamining) {
14619             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14620             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14621             SendToICS(buf);
14622         } else {
14623             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14624                 if(x == BOARD_LEFT-2) {
14625                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14626                     boards[0][y][1] = 0;
14627                 } else
14628                 if(x == BOARD_RGHT+1) {
14629                     if(y >= gameInfo.holdingsSize) break;
14630                     boards[0][y][BOARD_WIDTH-2] = 0;
14631                 } else break;
14632             }
14633             boards[0][y][x] = EmptySquare;
14634             DrawPosition(FALSE, boards[0]);
14635         }
14636         break;
14637
14638       case PromotePiece:
14639         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14640            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14641             selection = (ChessSquare) (PROMOTED piece);
14642         } else if(piece == EmptySquare) selection = WhiteSilver;
14643         else selection = (ChessSquare)((int)piece - 1);
14644         goto defaultlabel;
14645
14646       case DemotePiece:
14647         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14648            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14649             selection = (ChessSquare) (DEMOTED piece);
14650         } else if(piece == EmptySquare) selection = BlackSilver;
14651         else selection = (ChessSquare)((int)piece + 1);
14652         goto defaultlabel;
14653
14654       case WhiteQueen:
14655       case BlackQueen:
14656         if(gameInfo.variant == VariantShatranj ||
14657            gameInfo.variant == VariantXiangqi  ||
14658            gameInfo.variant == VariantCourier  ||
14659            gameInfo.variant == VariantASEAN    ||
14660            gameInfo.variant == VariantMakruk     )
14661             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14662         goto defaultlabel;
14663
14664       case WhiteKing:
14665       case BlackKing:
14666         if(gameInfo.variant == VariantXiangqi)
14667             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14668         if(gameInfo.variant == VariantKnightmate)
14669             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14670       default:
14671         defaultlabel:
14672         if (gameMode == IcsExamining) {
14673             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14674             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14675                      PieceToChar(selection), AAA + x, ONE + y);
14676             SendToICS(buf);
14677         } else {
14678             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14679                 int n;
14680                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14681                     n = PieceToNumber(selection - BlackPawn);
14682                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14683                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14684                     boards[0][BOARD_HEIGHT-1-n][1]++;
14685                 } else
14686                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14687                     n = PieceToNumber(selection);
14688                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14689                     boards[0][n][BOARD_WIDTH-1] = selection;
14690                     boards[0][n][BOARD_WIDTH-2]++;
14691                 }
14692             } else
14693             boards[0][y][x] = selection;
14694             DrawPosition(TRUE, boards[0]);
14695             ClearHighlights();
14696             fromX = fromY = -1;
14697         }
14698         break;
14699     }
14700 }
14701
14702
14703 void
14704 DropMenuEvent (ChessSquare selection, int x, int y)
14705 {
14706     ChessMove moveType;
14707
14708     switch (gameMode) {
14709       case IcsPlayingWhite:
14710       case MachinePlaysBlack:
14711         if (!WhiteOnMove(currentMove)) {
14712             DisplayMoveError(_("It is Black's turn"));
14713             return;
14714         }
14715         moveType = WhiteDrop;
14716         break;
14717       case IcsPlayingBlack:
14718       case MachinePlaysWhite:
14719         if (WhiteOnMove(currentMove)) {
14720             DisplayMoveError(_("It is White's turn"));
14721             return;
14722         }
14723         moveType = BlackDrop;
14724         break;
14725       case EditGame:
14726         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14727         break;
14728       default:
14729         return;
14730     }
14731
14732     if (moveType == BlackDrop && selection < BlackPawn) {
14733       selection = (ChessSquare) ((int) selection
14734                                  + (int) BlackPawn - (int) WhitePawn);
14735     }
14736     if (boards[currentMove][y][x] != EmptySquare) {
14737         DisplayMoveError(_("That square is occupied"));
14738         return;
14739     }
14740
14741     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14742 }
14743
14744 void
14745 AcceptEvent ()
14746 {
14747     /* Accept a pending offer of any kind from opponent */
14748
14749     if (appData.icsActive) {
14750         SendToICS(ics_prefix);
14751         SendToICS("accept\n");
14752     } else if (cmailMsgLoaded) {
14753         if (currentMove == cmailOldMove &&
14754             commentList[cmailOldMove] != NULL &&
14755             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14756                    "Black offers a draw" : "White offers a draw")) {
14757             TruncateGame();
14758             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14759             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14760         } else {
14761             DisplayError(_("There is no pending offer on this move"), 0);
14762             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14763         }
14764     } else {
14765         /* Not used for offers from chess program */
14766     }
14767 }
14768
14769 void
14770 DeclineEvent ()
14771 {
14772     /* Decline a pending offer of any kind from opponent */
14773
14774     if (appData.icsActive) {
14775         SendToICS(ics_prefix);
14776         SendToICS("decline\n");
14777     } else if (cmailMsgLoaded) {
14778         if (currentMove == cmailOldMove &&
14779             commentList[cmailOldMove] != NULL &&
14780             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14781                    "Black offers a draw" : "White offers a draw")) {
14782 #ifdef NOTDEF
14783             AppendComment(cmailOldMove, "Draw declined", TRUE);
14784             DisplayComment(cmailOldMove - 1, "Draw declined");
14785 #endif /*NOTDEF*/
14786         } else {
14787             DisplayError(_("There is no pending offer on this move"), 0);
14788         }
14789     } else {
14790         /* Not used for offers from chess program */
14791     }
14792 }
14793
14794 void
14795 RematchEvent ()
14796 {
14797     /* Issue ICS rematch command */
14798     if (appData.icsActive) {
14799         SendToICS(ics_prefix);
14800         SendToICS("rematch\n");
14801     }
14802 }
14803
14804 void
14805 CallFlagEvent ()
14806 {
14807     /* Call your opponent's flag (claim a win on time) */
14808     if (appData.icsActive) {
14809         SendToICS(ics_prefix);
14810         SendToICS("flag\n");
14811     } else {
14812         switch (gameMode) {
14813           default:
14814             return;
14815           case MachinePlaysWhite:
14816             if (whiteFlag) {
14817                 if (blackFlag)
14818                   GameEnds(GameIsDrawn, "Both players ran out of time",
14819                            GE_PLAYER);
14820                 else
14821                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14822             } else {
14823                 DisplayError(_("Your opponent is not out of time"), 0);
14824             }
14825             break;
14826           case MachinePlaysBlack:
14827             if (blackFlag) {
14828                 if (whiteFlag)
14829                   GameEnds(GameIsDrawn, "Both players ran out of time",
14830                            GE_PLAYER);
14831                 else
14832                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14833             } else {
14834                 DisplayError(_("Your opponent is not out of time"), 0);
14835             }
14836             break;
14837         }
14838     }
14839 }
14840
14841 void
14842 ClockClick (int which)
14843 {       // [HGM] code moved to back-end from winboard.c
14844         if(which) { // black clock
14845           if (gameMode == EditPosition || gameMode == IcsExamining) {
14846             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14847             SetBlackToPlayEvent();
14848           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14849           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14850           } else if (shiftKey) {
14851             AdjustClock(which, -1);
14852           } else if (gameMode == IcsPlayingWhite ||
14853                      gameMode == MachinePlaysBlack) {
14854             CallFlagEvent();
14855           }
14856         } else { // white clock
14857           if (gameMode == EditPosition || gameMode == IcsExamining) {
14858             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14859             SetWhiteToPlayEvent();
14860           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14861           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14862           } else if (shiftKey) {
14863             AdjustClock(which, -1);
14864           } else if (gameMode == IcsPlayingBlack ||
14865                    gameMode == MachinePlaysWhite) {
14866             CallFlagEvent();
14867           }
14868         }
14869 }
14870
14871 void
14872 DrawEvent ()
14873 {
14874     /* Offer draw or accept pending draw offer from opponent */
14875
14876     if (appData.icsActive) {
14877         /* Note: tournament rules require draw offers to be
14878            made after you make your move but before you punch
14879            your clock.  Currently ICS doesn't let you do that;
14880            instead, you immediately punch your clock after making
14881            a move, but you can offer a draw at any time. */
14882
14883         SendToICS(ics_prefix);
14884         SendToICS("draw\n");
14885         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14886     } else if (cmailMsgLoaded) {
14887         if (currentMove == cmailOldMove &&
14888             commentList[cmailOldMove] != NULL &&
14889             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14890                    "Black offers a draw" : "White offers a draw")) {
14891             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14892             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14893         } else if (currentMove == cmailOldMove + 1) {
14894             char *offer = WhiteOnMove(cmailOldMove) ?
14895               "White offers a draw" : "Black offers a draw";
14896             AppendComment(currentMove, offer, TRUE);
14897             DisplayComment(currentMove - 1, offer);
14898             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14899         } else {
14900             DisplayError(_("You must make your move before offering a draw"), 0);
14901             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14902         }
14903     } else if (first.offeredDraw) {
14904         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14905     } else {
14906         if (first.sendDrawOffers) {
14907             SendToProgram("draw\n", &first);
14908             userOfferedDraw = TRUE;
14909         }
14910     }
14911 }
14912
14913 void
14914 AdjournEvent ()
14915 {
14916     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14917
14918     if (appData.icsActive) {
14919         SendToICS(ics_prefix);
14920         SendToICS("adjourn\n");
14921     } else {
14922         /* Currently GNU Chess doesn't offer or accept Adjourns */
14923     }
14924 }
14925
14926
14927 void
14928 AbortEvent ()
14929 {
14930     /* Offer Abort or accept pending Abort offer from opponent */
14931
14932     if (appData.icsActive) {
14933         SendToICS(ics_prefix);
14934         SendToICS("abort\n");
14935     } else {
14936         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14937     }
14938 }
14939
14940 void
14941 ResignEvent ()
14942 {
14943     /* Resign.  You can do this even if it's not your turn. */
14944
14945     if (appData.icsActive) {
14946         SendToICS(ics_prefix);
14947         SendToICS("resign\n");
14948     } else {
14949         switch (gameMode) {
14950           case MachinePlaysWhite:
14951             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14952             break;
14953           case MachinePlaysBlack:
14954             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14955             break;
14956           case EditGame:
14957             if (cmailMsgLoaded) {
14958                 TruncateGame();
14959                 if (WhiteOnMove(cmailOldMove)) {
14960                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14961                 } else {
14962                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14963                 }
14964                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14965             }
14966             break;
14967           default:
14968             break;
14969         }
14970     }
14971 }
14972
14973
14974 void
14975 StopObservingEvent ()
14976 {
14977     /* Stop observing current games */
14978     SendToICS(ics_prefix);
14979     SendToICS("unobserve\n");
14980 }
14981
14982 void
14983 StopExaminingEvent ()
14984 {
14985     /* Stop observing current game */
14986     SendToICS(ics_prefix);
14987     SendToICS("unexamine\n");
14988 }
14989
14990 void
14991 ForwardInner (int target)
14992 {
14993     int limit; int oldSeekGraphUp = seekGraphUp;
14994
14995     if (appData.debugMode)
14996         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14997                 target, currentMove, forwardMostMove);
14998
14999     if (gameMode == EditPosition)
15000       return;
15001
15002     seekGraphUp = FALSE;
15003     MarkTargetSquares(1);
15004
15005     if (gameMode == PlayFromGameFile && !pausing)
15006       PauseEvent();
15007
15008     if (gameMode == IcsExamining && pausing)
15009       limit = pauseExamForwardMostMove;
15010     else
15011       limit = forwardMostMove;
15012
15013     if (target > limit) target = limit;
15014
15015     if (target > 0 && moveList[target - 1][0]) {
15016         int fromX, fromY, toX, toY;
15017         toX = moveList[target - 1][2] - AAA;
15018         toY = moveList[target - 1][3] - ONE;
15019         if (moveList[target - 1][1] == '@') {
15020             if (appData.highlightLastMove) {
15021                 SetHighlights(-1, -1, toX, toY);
15022             }
15023         } else {
15024             fromX = moveList[target - 1][0] - AAA;
15025             fromY = moveList[target - 1][1] - ONE;
15026             if (target == currentMove + 1) {
15027                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15028             }
15029             if (appData.highlightLastMove) {
15030                 SetHighlights(fromX, fromY, toX, toY);
15031             }
15032         }
15033     }
15034     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15035         gameMode == Training || gameMode == PlayFromGameFile ||
15036         gameMode == AnalyzeFile) {
15037         while (currentMove < target) {
15038             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15039             SendMoveToProgram(currentMove++, &first);
15040         }
15041     } else {
15042         currentMove = target;
15043     }
15044
15045     if (gameMode == EditGame || gameMode == EndOfGame) {
15046         whiteTimeRemaining = timeRemaining[0][currentMove];
15047         blackTimeRemaining = timeRemaining[1][currentMove];
15048     }
15049     DisplayBothClocks();
15050     DisplayMove(currentMove - 1);
15051     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15052     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15053     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15054         DisplayComment(currentMove - 1, commentList[currentMove]);
15055     }
15056     ClearMap(); // [HGM] exclude: invalidate map
15057 }
15058
15059
15060 void
15061 ForwardEvent ()
15062 {
15063     if (gameMode == IcsExamining && !pausing) {
15064         SendToICS(ics_prefix);
15065         SendToICS("forward\n");
15066     } else {
15067         ForwardInner(currentMove + 1);
15068     }
15069 }
15070
15071 void
15072 ToEndEvent ()
15073 {
15074     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15075         /* to optimze, we temporarily turn off analysis mode while we feed
15076          * the remaining moves to the engine. Otherwise we get analysis output
15077          * after each move.
15078          */
15079         if (first.analysisSupport) {
15080           SendToProgram("exit\nforce\n", &first);
15081           first.analyzing = FALSE;
15082         }
15083     }
15084
15085     if (gameMode == IcsExamining && !pausing) {
15086         SendToICS(ics_prefix);
15087         SendToICS("forward 999999\n");
15088     } else {
15089         ForwardInner(forwardMostMove);
15090     }
15091
15092     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15093         /* we have fed all the moves, so reactivate analysis mode */
15094         SendToProgram("analyze\n", &first);
15095         first.analyzing = TRUE;
15096         /*first.maybeThinking = TRUE;*/
15097         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15098     }
15099 }
15100
15101 void
15102 BackwardInner (int target)
15103 {
15104     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15105
15106     if (appData.debugMode)
15107         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15108                 target, currentMove, forwardMostMove);
15109
15110     if (gameMode == EditPosition) return;
15111     seekGraphUp = FALSE;
15112     MarkTargetSquares(1);
15113     if (currentMove <= backwardMostMove) {
15114         ClearHighlights();
15115         DrawPosition(full_redraw, boards[currentMove]);
15116         return;
15117     }
15118     if (gameMode == PlayFromGameFile && !pausing)
15119       PauseEvent();
15120
15121     if (moveList[target][0]) {
15122         int fromX, fromY, toX, toY;
15123         toX = moveList[target][2] - AAA;
15124         toY = moveList[target][3] - ONE;
15125         if (moveList[target][1] == '@') {
15126             if (appData.highlightLastMove) {
15127                 SetHighlights(-1, -1, toX, toY);
15128             }
15129         } else {
15130             fromX = moveList[target][0] - AAA;
15131             fromY = moveList[target][1] - ONE;
15132             if (target == currentMove - 1) {
15133                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15134             }
15135             if (appData.highlightLastMove) {
15136                 SetHighlights(fromX, fromY, toX, toY);
15137             }
15138         }
15139     }
15140     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15141         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15142         while (currentMove > target) {
15143             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15144                 // null move cannot be undone. Reload program with move history before it.
15145                 int i;
15146                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15147                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15148                 }
15149                 SendBoard(&first, i);
15150               if(second.analyzing) SendBoard(&second, i);
15151                 for(currentMove=i; currentMove<target; currentMove++) {
15152                     SendMoveToProgram(currentMove, &first);
15153                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15154                 }
15155                 break;
15156             }
15157             SendToBoth("undo\n");
15158             currentMove--;
15159         }
15160     } else {
15161         currentMove = target;
15162     }
15163
15164     if (gameMode == EditGame || gameMode == EndOfGame) {
15165         whiteTimeRemaining = timeRemaining[0][currentMove];
15166         blackTimeRemaining = timeRemaining[1][currentMove];
15167     }
15168     DisplayBothClocks();
15169     DisplayMove(currentMove - 1);
15170     DrawPosition(full_redraw, boards[currentMove]);
15171     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15172     // [HGM] PV info: routine tests if comment empty
15173     DisplayComment(currentMove - 1, commentList[currentMove]);
15174     ClearMap(); // [HGM] exclude: invalidate map
15175 }
15176
15177 void
15178 BackwardEvent ()
15179 {
15180     if (gameMode == IcsExamining && !pausing) {
15181         SendToICS(ics_prefix);
15182         SendToICS("backward\n");
15183     } else {
15184         BackwardInner(currentMove - 1);
15185     }
15186 }
15187
15188 void
15189 ToStartEvent ()
15190 {
15191     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15192         /* to optimize, we temporarily turn off analysis mode while we undo
15193          * all the moves. Otherwise we get analysis output after each undo.
15194          */
15195         if (first.analysisSupport) {
15196           SendToProgram("exit\nforce\n", &first);
15197           first.analyzing = FALSE;
15198         }
15199     }
15200
15201     if (gameMode == IcsExamining && !pausing) {
15202         SendToICS(ics_prefix);
15203         SendToICS("backward 999999\n");
15204     } else {
15205         BackwardInner(backwardMostMove);
15206     }
15207
15208     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15209         /* we have fed all the moves, so reactivate analysis mode */
15210         SendToProgram("analyze\n", &first);
15211         first.analyzing = TRUE;
15212         /*first.maybeThinking = TRUE;*/
15213         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15214     }
15215 }
15216
15217 void
15218 ToNrEvent (int to)
15219 {
15220   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15221   if (to >= forwardMostMove) to = forwardMostMove;
15222   if (to <= backwardMostMove) to = backwardMostMove;
15223   if (to < currentMove) {
15224     BackwardInner(to);
15225   } else {
15226     ForwardInner(to);
15227   }
15228 }
15229
15230 void
15231 RevertEvent (Boolean annotate)
15232 {
15233     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15234         return;
15235     }
15236     if (gameMode != IcsExamining) {
15237         DisplayError(_("You are not examining a game"), 0);
15238         return;
15239     }
15240     if (pausing) {
15241         DisplayError(_("You can't revert while pausing"), 0);
15242         return;
15243     }
15244     SendToICS(ics_prefix);
15245     SendToICS("revert\n");
15246 }
15247
15248 void
15249 RetractMoveEvent ()
15250 {
15251     switch (gameMode) {
15252       case MachinePlaysWhite:
15253       case MachinePlaysBlack:
15254         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15255             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15256             return;
15257         }
15258         if (forwardMostMove < 2) return;
15259         currentMove = forwardMostMove = forwardMostMove - 2;
15260         whiteTimeRemaining = timeRemaining[0][currentMove];
15261         blackTimeRemaining = timeRemaining[1][currentMove];
15262         DisplayBothClocks();
15263         DisplayMove(currentMove - 1);
15264         ClearHighlights();/*!! could figure this out*/
15265         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15266         SendToProgram("remove\n", &first);
15267         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15268         break;
15269
15270       case BeginningOfGame:
15271       default:
15272         break;
15273
15274       case IcsPlayingWhite:
15275       case IcsPlayingBlack:
15276         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15277             SendToICS(ics_prefix);
15278             SendToICS("takeback 2\n");
15279         } else {
15280             SendToICS(ics_prefix);
15281             SendToICS("takeback 1\n");
15282         }
15283         break;
15284     }
15285 }
15286
15287 void
15288 MoveNowEvent ()
15289 {
15290     ChessProgramState *cps;
15291
15292     switch (gameMode) {
15293       case MachinePlaysWhite:
15294         if (!WhiteOnMove(forwardMostMove)) {
15295             DisplayError(_("It is your turn"), 0);
15296             return;
15297         }
15298         cps = &first;
15299         break;
15300       case MachinePlaysBlack:
15301         if (WhiteOnMove(forwardMostMove)) {
15302             DisplayError(_("It is your turn"), 0);
15303             return;
15304         }
15305         cps = &first;
15306         break;
15307       case TwoMachinesPlay:
15308         if (WhiteOnMove(forwardMostMove) ==
15309             (first.twoMachinesColor[0] == 'w')) {
15310             cps = &first;
15311         } else {
15312             cps = &second;
15313         }
15314         break;
15315       case BeginningOfGame:
15316       default:
15317         return;
15318     }
15319     SendToProgram("?\n", cps);
15320 }
15321
15322 void
15323 TruncateGameEvent ()
15324 {
15325     EditGameEvent();
15326     if (gameMode != EditGame) return;
15327     TruncateGame();
15328 }
15329
15330 void
15331 TruncateGame ()
15332 {
15333     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15334     if (forwardMostMove > currentMove) {
15335         if (gameInfo.resultDetails != NULL) {
15336             free(gameInfo.resultDetails);
15337             gameInfo.resultDetails = NULL;
15338             gameInfo.result = GameUnfinished;
15339         }
15340         forwardMostMove = currentMove;
15341         HistorySet(parseList, backwardMostMove, forwardMostMove,
15342                    currentMove-1);
15343     }
15344 }
15345
15346 void
15347 HintEvent ()
15348 {
15349     if (appData.noChessProgram) return;
15350     switch (gameMode) {
15351       case MachinePlaysWhite:
15352         if (WhiteOnMove(forwardMostMove)) {
15353             DisplayError(_("Wait until your turn"), 0);
15354             return;
15355         }
15356         break;
15357       case BeginningOfGame:
15358       case MachinePlaysBlack:
15359         if (!WhiteOnMove(forwardMostMove)) {
15360             DisplayError(_("Wait until your turn"), 0);
15361             return;
15362         }
15363         break;
15364       default:
15365         DisplayError(_("No hint available"), 0);
15366         return;
15367     }
15368     SendToProgram("hint\n", &first);
15369     hintRequested = TRUE;
15370 }
15371
15372 void
15373 CreateBookEvent ()
15374 {
15375     ListGame * lg = (ListGame *) gameList.head;
15376     FILE *f, *g;
15377     int nItem;
15378     static int secondTime = FALSE;
15379
15380     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15381         DisplayError(_("Game list not loaded or empty"), 0);
15382         return;
15383     }
15384
15385     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15386         fclose(g);
15387         secondTime++;
15388         DisplayNote(_("Book file exists! Try again for overwrite."));
15389         return;
15390     }
15391
15392     creatingBook = TRUE;
15393     secondTime = FALSE;
15394
15395     /* Get list size */
15396     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15397         LoadGame(f, nItem, "", TRUE);
15398         AddGameToBook(TRUE);
15399         lg = (ListGame *) lg->node.succ;
15400     }
15401
15402     creatingBook = FALSE;
15403     FlushBook();
15404 }
15405
15406 void
15407 BookEvent ()
15408 {
15409     if (appData.noChessProgram) return;
15410     switch (gameMode) {
15411       case MachinePlaysWhite:
15412         if (WhiteOnMove(forwardMostMove)) {
15413             DisplayError(_("Wait until your turn"), 0);
15414             return;
15415         }
15416         break;
15417       case BeginningOfGame:
15418       case MachinePlaysBlack:
15419         if (!WhiteOnMove(forwardMostMove)) {
15420             DisplayError(_("Wait until your turn"), 0);
15421             return;
15422         }
15423         break;
15424       case EditPosition:
15425         EditPositionDone(TRUE);
15426         break;
15427       case TwoMachinesPlay:
15428         return;
15429       default:
15430         break;
15431     }
15432     SendToProgram("bk\n", &first);
15433     bookOutput[0] = NULLCHAR;
15434     bookRequested = TRUE;
15435 }
15436
15437 void
15438 AboutGameEvent ()
15439 {
15440     char *tags = PGNTags(&gameInfo);
15441     TagsPopUp(tags, CmailMsg());
15442     free(tags);
15443 }
15444
15445 /* end button procedures */
15446
15447 void
15448 PrintPosition (FILE *fp, int move)
15449 {
15450     int i, j;
15451
15452     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15453         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15454             char c = PieceToChar(boards[move][i][j]);
15455             fputc(c == 'x' ? '.' : c, fp);
15456             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15457         }
15458     }
15459     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15460       fprintf(fp, "white to play\n");
15461     else
15462       fprintf(fp, "black to play\n");
15463 }
15464
15465 void
15466 PrintOpponents (FILE *fp)
15467 {
15468     if (gameInfo.white != NULL) {
15469         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15470     } else {
15471         fprintf(fp, "\n");
15472     }
15473 }
15474
15475 /* Find last component of program's own name, using some heuristics */
15476 void
15477 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15478 {
15479     char *p, *q, c;
15480     int local = (strcmp(host, "localhost") == 0);
15481     while (!local && (p = strchr(prog, ';')) != NULL) {
15482         p++;
15483         while (*p == ' ') p++;
15484         prog = p;
15485     }
15486     if (*prog == '"' || *prog == '\'') {
15487         q = strchr(prog + 1, *prog);
15488     } else {
15489         q = strchr(prog, ' ');
15490     }
15491     if (q == NULL) q = prog + strlen(prog);
15492     p = q;
15493     while (p >= prog && *p != '/' && *p != '\\') p--;
15494     p++;
15495     if(p == prog && *p == '"') p++;
15496     c = *q; *q = 0;
15497     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15498     memcpy(buf, p, q - p);
15499     buf[q - p] = NULLCHAR;
15500     if (!local) {
15501         strcat(buf, "@");
15502         strcat(buf, host);
15503     }
15504 }
15505
15506 char *
15507 TimeControlTagValue ()
15508 {
15509     char buf[MSG_SIZ];
15510     if (!appData.clockMode) {
15511       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15512     } else if (movesPerSession > 0) {
15513       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15514     } else if (timeIncrement == 0) {
15515       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15516     } else {
15517       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15518     }
15519     return StrSave(buf);
15520 }
15521
15522 void
15523 SetGameInfo ()
15524 {
15525     /* This routine is used only for certain modes */
15526     VariantClass v = gameInfo.variant;
15527     ChessMove r = GameUnfinished;
15528     char *p = NULL;
15529
15530     if(keepInfo) return;
15531
15532     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15533         r = gameInfo.result;
15534         p = gameInfo.resultDetails;
15535         gameInfo.resultDetails = NULL;
15536     }
15537     ClearGameInfo(&gameInfo);
15538     gameInfo.variant = v;
15539
15540     switch (gameMode) {
15541       case MachinePlaysWhite:
15542         gameInfo.event = StrSave( appData.pgnEventHeader );
15543         gameInfo.site = StrSave(HostName());
15544         gameInfo.date = PGNDate();
15545         gameInfo.round = StrSave("-");
15546         gameInfo.white = StrSave(first.tidy);
15547         gameInfo.black = StrSave(UserName());
15548         gameInfo.timeControl = TimeControlTagValue();
15549         break;
15550
15551       case MachinePlaysBlack:
15552         gameInfo.event = StrSave( appData.pgnEventHeader );
15553         gameInfo.site = StrSave(HostName());
15554         gameInfo.date = PGNDate();
15555         gameInfo.round = StrSave("-");
15556         gameInfo.white = StrSave(UserName());
15557         gameInfo.black = StrSave(first.tidy);
15558         gameInfo.timeControl = TimeControlTagValue();
15559         break;
15560
15561       case TwoMachinesPlay:
15562         gameInfo.event = StrSave( appData.pgnEventHeader );
15563         gameInfo.site = StrSave(HostName());
15564         gameInfo.date = PGNDate();
15565         if (roundNr > 0) {
15566             char buf[MSG_SIZ];
15567             snprintf(buf, MSG_SIZ, "%d", roundNr);
15568             gameInfo.round = StrSave(buf);
15569         } else {
15570             gameInfo.round = StrSave("-");
15571         }
15572         if (first.twoMachinesColor[0] == 'w') {
15573             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15574             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15575         } else {
15576             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15577             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15578         }
15579         gameInfo.timeControl = TimeControlTagValue();
15580         break;
15581
15582       case EditGame:
15583         gameInfo.event = StrSave("Edited game");
15584         gameInfo.site = StrSave(HostName());
15585         gameInfo.date = PGNDate();
15586         gameInfo.round = StrSave("-");
15587         gameInfo.white = StrSave("-");
15588         gameInfo.black = StrSave("-");
15589         gameInfo.result = r;
15590         gameInfo.resultDetails = p;
15591         break;
15592
15593       case EditPosition:
15594         gameInfo.event = StrSave("Edited position");
15595         gameInfo.site = StrSave(HostName());
15596         gameInfo.date = PGNDate();
15597         gameInfo.round = StrSave("-");
15598         gameInfo.white = StrSave("-");
15599         gameInfo.black = StrSave("-");
15600         break;
15601
15602       case IcsPlayingWhite:
15603       case IcsPlayingBlack:
15604       case IcsObserving:
15605       case IcsExamining:
15606         break;
15607
15608       case PlayFromGameFile:
15609         gameInfo.event = StrSave("Game from non-PGN file");
15610         gameInfo.site = StrSave(HostName());
15611         gameInfo.date = PGNDate();
15612         gameInfo.round = StrSave("-");
15613         gameInfo.white = StrSave("?");
15614         gameInfo.black = StrSave("?");
15615         break;
15616
15617       default:
15618         break;
15619     }
15620 }
15621
15622 void
15623 ReplaceComment (int index, char *text)
15624 {
15625     int len;
15626     char *p;
15627     float score;
15628
15629     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15630        pvInfoList[index-1].depth == len &&
15631        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15632        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15633     while (*text == '\n') text++;
15634     len = strlen(text);
15635     while (len > 0 && text[len - 1] == '\n') len--;
15636
15637     if (commentList[index] != NULL)
15638       free(commentList[index]);
15639
15640     if (len == 0) {
15641         commentList[index] = NULL;
15642         return;
15643     }
15644   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15645       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15646       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15647     commentList[index] = (char *) malloc(len + 2);
15648     strncpy(commentList[index], text, len);
15649     commentList[index][len] = '\n';
15650     commentList[index][len + 1] = NULLCHAR;
15651   } else {
15652     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15653     char *p;
15654     commentList[index] = (char *) malloc(len + 7);
15655     safeStrCpy(commentList[index], "{\n", 3);
15656     safeStrCpy(commentList[index]+2, text, len+1);
15657     commentList[index][len+2] = NULLCHAR;
15658     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15659     strcat(commentList[index], "\n}\n");
15660   }
15661 }
15662
15663 void
15664 CrushCRs (char *text)
15665 {
15666   char *p = text;
15667   char *q = text;
15668   char ch;
15669
15670   do {
15671     ch = *p++;
15672     if (ch == '\r') continue;
15673     *q++ = ch;
15674   } while (ch != '\0');
15675 }
15676
15677 void
15678 AppendComment (int index, char *text, Boolean addBraces)
15679 /* addBraces  tells if we should add {} */
15680 {
15681     int oldlen, len;
15682     char *old;
15683
15684 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15685     if(addBraces == 3) addBraces = 0; else // force appending literally
15686     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15687
15688     CrushCRs(text);
15689     while (*text == '\n') text++;
15690     len = strlen(text);
15691     while (len > 0 && text[len - 1] == '\n') len--;
15692     text[len] = NULLCHAR;
15693
15694     if (len == 0) return;
15695
15696     if (commentList[index] != NULL) {
15697       Boolean addClosingBrace = addBraces;
15698         old = commentList[index];
15699         oldlen = strlen(old);
15700         while(commentList[index][oldlen-1] ==  '\n')
15701           commentList[index][--oldlen] = NULLCHAR;
15702         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15703         safeStrCpy(commentList[index], old, oldlen + len + 6);
15704         free(old);
15705         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15706         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15707           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15708           while (*text == '\n') { text++; len--; }
15709           commentList[index][--oldlen] = NULLCHAR;
15710       }
15711         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15712         else          strcat(commentList[index], "\n");
15713         strcat(commentList[index], text);
15714         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15715         else          strcat(commentList[index], "\n");
15716     } else {
15717         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15718         if(addBraces)
15719           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15720         else commentList[index][0] = NULLCHAR;
15721         strcat(commentList[index], text);
15722         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15723         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15724     }
15725 }
15726
15727 static char *
15728 FindStr (char * text, char * sub_text)
15729 {
15730     char * result = strstr( text, sub_text );
15731
15732     if( result != NULL ) {
15733         result += strlen( sub_text );
15734     }
15735
15736     return result;
15737 }
15738
15739 /* [AS] Try to extract PV info from PGN comment */
15740 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15741 char *
15742 GetInfoFromComment (int index, char * text)
15743 {
15744     char * sep = text, *p;
15745
15746     if( text != NULL && index > 0 ) {
15747         int score = 0;
15748         int depth = 0;
15749         int time = -1, sec = 0, deci;
15750         char * s_eval = FindStr( text, "[%eval " );
15751         char * s_emt = FindStr( text, "[%emt " );
15752 #if 0
15753         if( s_eval != NULL || s_emt != NULL ) {
15754 #else
15755         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15756 #endif
15757             /* New style */
15758             char delim;
15759
15760             if( s_eval != NULL ) {
15761                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15762                     return text;
15763                 }
15764
15765                 if( delim != ']' ) {
15766                     return text;
15767                 }
15768             }
15769
15770             if( s_emt != NULL ) {
15771             }
15772                 return text;
15773         }
15774         else {
15775             /* We expect something like: [+|-]nnn.nn/dd */
15776             int score_lo = 0;
15777
15778             if(*text != '{') return text; // [HGM] braces: must be normal comment
15779
15780             sep = strchr( text, '/' );
15781             if( sep == NULL || sep < (text+4) ) {
15782                 return text;
15783             }
15784
15785             p = text;
15786             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15787             if(p[1] == '(') { // comment starts with PV
15788                p = strchr(p, ')'); // locate end of PV
15789                if(p == NULL || sep < p+5) return text;
15790                // at this point we have something like "{(.*) +0.23/6 ..."
15791                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15792                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15793                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15794             }
15795             time = -1; sec = -1; deci = -1;
15796             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15797                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15798                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15799                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15800                 return text;
15801             }
15802
15803             if( score_lo < 0 || score_lo >= 100 ) {
15804                 return text;
15805             }
15806
15807             if(sec >= 0) time = 600*time + 10*sec; else
15808             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15809
15810             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15811
15812             /* [HGM] PV time: now locate end of PV info */
15813             while( *++sep >= '0' && *sep <= '9'); // strip depth
15814             if(time >= 0)
15815             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15816             if(sec >= 0)
15817             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15818             if(deci >= 0)
15819             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15820             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15821         }
15822
15823         if( depth <= 0 ) {
15824             return text;
15825         }
15826
15827         if( time < 0 ) {
15828             time = -1;
15829         }
15830
15831         pvInfoList[index-1].depth = depth;
15832         pvInfoList[index-1].score = score;
15833         pvInfoList[index-1].time  = 10*time; // centi-sec
15834         if(*sep == '}') *sep = 0; else *--sep = '{';
15835         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15836     }
15837     return sep;
15838 }
15839
15840 void
15841 SendToProgram (char *message, ChessProgramState *cps)
15842 {
15843     int count, outCount, error;
15844     char buf[MSG_SIZ];
15845
15846     if (cps->pr == NoProc) return;
15847     Attention(cps);
15848
15849     if (appData.debugMode) {
15850         TimeMark now;
15851         GetTimeMark(&now);
15852         fprintf(debugFP, "%ld >%-6s: %s",
15853                 SubtractTimeMarks(&now, &programStartTime),
15854                 cps->which, message);
15855         if(serverFP)
15856             fprintf(serverFP, "%ld >%-6s: %s",
15857                 SubtractTimeMarks(&now, &programStartTime),
15858                 cps->which, message), fflush(serverFP);
15859     }
15860
15861     count = strlen(message);
15862     outCount = OutputToProcess(cps->pr, message, count, &error);
15863     if (outCount < count && !exiting
15864                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15865       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15866       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15867         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15868             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15869                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15870                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15871                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15872             } else {
15873                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15874                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15875                 gameInfo.result = res;
15876             }
15877             gameInfo.resultDetails = StrSave(buf);
15878         }
15879         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15880         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15881     }
15882 }
15883
15884 void
15885 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15886 {
15887     char *end_str;
15888     char buf[MSG_SIZ];
15889     ChessProgramState *cps = (ChessProgramState *)closure;
15890
15891     if (isr != cps->isr) return; /* Killed intentionally */
15892     if (count <= 0) {
15893         if (count == 0) {
15894             RemoveInputSource(cps->isr);
15895             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15896                     _(cps->which), cps->program);
15897             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15898             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15899                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15900                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15901                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15902                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15903                 } else {
15904                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15905                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15906                     gameInfo.result = res;
15907                 }
15908                 gameInfo.resultDetails = StrSave(buf);
15909             }
15910             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15911             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15912         } else {
15913             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15914                     _(cps->which), cps->program);
15915             RemoveInputSource(cps->isr);
15916
15917             /* [AS] Program is misbehaving badly... kill it */
15918             if( count == -2 ) {
15919                 DestroyChildProcess( cps->pr, 9 );
15920                 cps->pr = NoProc;
15921             }
15922
15923             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15924         }
15925         return;
15926     }
15927
15928     if ((end_str = strchr(message, '\r')) != NULL)
15929       *end_str = NULLCHAR;
15930     if ((end_str = strchr(message, '\n')) != NULL)
15931       *end_str = NULLCHAR;
15932
15933     if (appData.debugMode) {
15934         TimeMark now; int print = 1;
15935         char *quote = ""; char c; int i;
15936
15937         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15938                 char start = message[0];
15939                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15940                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15941                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15942                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15943                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15944                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15945                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15946                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15947                    sscanf(message, "hint: %c", &c)!=1 &&
15948                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15949                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15950                     print = (appData.engineComments >= 2);
15951                 }
15952                 message[0] = start; // restore original message
15953         }
15954         if(print) {
15955                 GetTimeMark(&now);
15956                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15957                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15958                         quote,
15959                         message);
15960                 if(serverFP)
15961                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15962                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15963                         quote,
15964                         message), fflush(serverFP);
15965         }
15966     }
15967
15968     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15969     if (appData.icsEngineAnalyze) {
15970         if (strstr(message, "whisper") != NULL ||
15971              strstr(message, "kibitz") != NULL ||
15972             strstr(message, "tellics") != NULL) return;
15973     }
15974
15975     HandleMachineMove(message, cps);
15976 }
15977
15978
15979 void
15980 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15981 {
15982     char buf[MSG_SIZ];
15983     int seconds;
15984
15985     if( timeControl_2 > 0 ) {
15986         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15987             tc = timeControl_2;
15988         }
15989     }
15990     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15991     inc /= cps->timeOdds;
15992     st  /= cps->timeOdds;
15993
15994     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15995
15996     if (st > 0) {
15997       /* Set exact time per move, normally using st command */
15998       if (cps->stKludge) {
15999         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16000         seconds = st % 60;
16001         if (seconds == 0) {
16002           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16003         } else {
16004           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16005         }
16006       } else {
16007         snprintf(buf, MSG_SIZ, "st %d\n", st);
16008       }
16009     } else {
16010       /* Set conventional or incremental time control, using level command */
16011       if (seconds == 0) {
16012         /* Note old gnuchess bug -- minutes:seconds used to not work.
16013            Fixed in later versions, but still avoid :seconds
16014            when seconds is 0. */
16015         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16016       } else {
16017         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16018                  seconds, inc/1000.);
16019       }
16020     }
16021     SendToProgram(buf, cps);
16022
16023     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16024     /* Orthogonally, limit search to given depth */
16025     if (sd > 0) {
16026       if (cps->sdKludge) {
16027         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16028       } else {
16029         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16030       }
16031       SendToProgram(buf, cps);
16032     }
16033
16034     if(cps->nps >= 0) { /* [HGM] nps */
16035         if(cps->supportsNPS == FALSE)
16036           cps->nps = -1; // don't use if engine explicitly says not supported!
16037         else {
16038           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16039           SendToProgram(buf, cps);
16040         }
16041     }
16042 }
16043
16044 ChessProgramState *
16045 WhitePlayer ()
16046 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16047 {
16048     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16049        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16050         return &second;
16051     return &first;
16052 }
16053
16054 void
16055 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16056 {
16057     char message[MSG_SIZ];
16058     long time, otime;
16059
16060     /* Note: this routine must be called when the clocks are stopped
16061        or when they have *just* been set or switched; otherwise
16062        it will be off by the time since the current tick started.
16063     */
16064     if (machineWhite) {
16065         time = whiteTimeRemaining / 10;
16066         otime = blackTimeRemaining / 10;
16067     } else {
16068         time = blackTimeRemaining / 10;
16069         otime = whiteTimeRemaining / 10;
16070     }
16071     /* [HGM] translate opponent's time by time-odds factor */
16072     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16073
16074     if (time <= 0) time = 1;
16075     if (otime <= 0) otime = 1;
16076
16077     snprintf(message, MSG_SIZ, "time %ld\n", time);
16078     SendToProgram(message, cps);
16079
16080     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16081     SendToProgram(message, cps);
16082 }
16083
16084 char *
16085 EngineDefinedVariant (ChessProgramState *cps, int n)
16086 {   // return name of n-th unknown variant that engine supports
16087     static char buf[MSG_SIZ];
16088     char *p, *s = cps->variants;
16089     if(!s) return NULL;
16090     do { // parse string from variants feature
16091       VariantClass v;
16092         p = strchr(s, ',');
16093         if(p) *p = NULLCHAR;
16094       v = StringToVariant(s);
16095       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16096         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16097             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16098         }
16099         if(p) *p++ = ',';
16100         if(n < 0) return buf;
16101     } while(s = p);
16102     return NULL;
16103 }
16104
16105 int
16106 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16107 {
16108   char buf[MSG_SIZ];
16109   int len = strlen(name);
16110   int val;
16111
16112   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16113     (*p) += len + 1;
16114     sscanf(*p, "%d", &val);
16115     *loc = (val != 0);
16116     while (**p && **p != ' ')
16117       (*p)++;
16118     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16119     SendToProgram(buf, cps);
16120     return TRUE;
16121   }
16122   return FALSE;
16123 }
16124
16125 int
16126 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16127 {
16128   char buf[MSG_SIZ];
16129   int len = strlen(name);
16130   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16131     (*p) += len + 1;
16132     sscanf(*p, "%d", loc);
16133     while (**p && **p != ' ') (*p)++;
16134     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16135     SendToProgram(buf, cps);
16136     return TRUE;
16137   }
16138   return FALSE;
16139 }
16140
16141 int
16142 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16143 {
16144   char buf[MSG_SIZ];
16145   int len = strlen(name);
16146   if (strncmp((*p), name, len) == 0
16147       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16148     (*p) += len + 2;
16149     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16150     sscanf(*p, "%[^\"]", *loc);
16151     while (**p && **p != '\"') (*p)++;
16152     if (**p == '\"') (*p)++;
16153     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16154     SendToProgram(buf, cps);
16155     return TRUE;
16156   }
16157   return FALSE;
16158 }
16159
16160 int
16161 ParseOption (Option *opt, ChessProgramState *cps)
16162 // [HGM] options: process the string that defines an engine option, and determine
16163 // name, type, default value, and allowed value range
16164 {
16165         char *p, *q, buf[MSG_SIZ];
16166         int n, min = (-1)<<31, max = 1<<31, def;
16167
16168         if(p = strstr(opt->name, " -spin ")) {
16169             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16170             if(max < min) max = min; // enforce consistency
16171             if(def < min) def = min;
16172             if(def > max) def = max;
16173             opt->value = def;
16174             opt->min = min;
16175             opt->max = max;
16176             opt->type = Spin;
16177         } else if((p = strstr(opt->name, " -slider "))) {
16178             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16179             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16180             if(max < min) max = min; // enforce consistency
16181             if(def < min) def = min;
16182             if(def > max) def = max;
16183             opt->value = def;
16184             opt->min = min;
16185             opt->max = max;
16186             opt->type = Spin; // Slider;
16187         } else if((p = strstr(opt->name, " -string "))) {
16188             opt->textValue = p+9;
16189             opt->type = TextBox;
16190         } else if((p = strstr(opt->name, " -file "))) {
16191             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16192             opt->textValue = p+7;
16193             opt->type = FileName; // FileName;
16194         } else if((p = strstr(opt->name, " -path "))) {
16195             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16196             opt->textValue = p+7;
16197             opt->type = PathName; // PathName;
16198         } else if(p = strstr(opt->name, " -check ")) {
16199             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16200             opt->value = (def != 0);
16201             opt->type = CheckBox;
16202         } else if(p = strstr(opt->name, " -combo ")) {
16203             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16204             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16205             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16206             opt->value = n = 0;
16207             while(q = StrStr(q, " /// ")) {
16208                 n++; *q = 0;    // count choices, and null-terminate each of them
16209                 q += 5;
16210                 if(*q == '*') { // remember default, which is marked with * prefix
16211                     q++;
16212                     opt->value = n;
16213                 }
16214                 cps->comboList[cps->comboCnt++] = q;
16215             }
16216             cps->comboList[cps->comboCnt++] = NULL;
16217             opt->max = n + 1;
16218             opt->type = ComboBox;
16219         } else if(p = strstr(opt->name, " -button")) {
16220             opt->type = Button;
16221         } else if(p = strstr(opt->name, " -save")) {
16222             opt->type = SaveButton;
16223         } else return FALSE;
16224         *p = 0; // terminate option name
16225         // now look if the command-line options define a setting for this engine option.
16226         if(cps->optionSettings && cps->optionSettings[0])
16227             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16228         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16229           snprintf(buf, MSG_SIZ, "option %s", p);
16230                 if(p = strstr(buf, ",")) *p = 0;
16231                 if(q = strchr(buf, '=')) switch(opt->type) {
16232                     case ComboBox:
16233                         for(n=0; n<opt->max; n++)
16234                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16235                         break;
16236                     case TextBox:
16237                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16238                         break;
16239                     case Spin:
16240                     case CheckBox:
16241                         opt->value = atoi(q+1);
16242                     default:
16243                         break;
16244                 }
16245                 strcat(buf, "\n");
16246                 SendToProgram(buf, cps);
16247         }
16248         return TRUE;
16249 }
16250
16251 void
16252 FeatureDone (ChessProgramState *cps, int val)
16253 {
16254   DelayedEventCallback cb = GetDelayedEvent();
16255   if ((cb == InitBackEnd3 && cps == &first) ||
16256       (cb == SettingsMenuIfReady && cps == &second) ||
16257       (cb == LoadEngine) ||
16258       (cb == TwoMachinesEventIfReady)) {
16259     CancelDelayedEvent();
16260     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16261   }
16262   cps->initDone = val;
16263   if(val) cps->reload = FALSE;
16264 }
16265
16266 /* Parse feature command from engine */
16267 void
16268 ParseFeatures (char *args, ChessProgramState *cps)
16269 {
16270   char *p = args;
16271   char *q = NULL;
16272   int val;
16273   char buf[MSG_SIZ];
16274
16275   for (;;) {
16276     while (*p == ' ') p++;
16277     if (*p == NULLCHAR) return;
16278
16279     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16280     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16281     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16282     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16283     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16284     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16285     if (BoolFeature(&p, "reuse", &val, cps)) {
16286       /* Engine can disable reuse, but can't enable it if user said no */
16287       if (!val) cps->reuse = FALSE;
16288       continue;
16289     }
16290     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16291     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16292       if (gameMode == TwoMachinesPlay) {
16293         DisplayTwoMachinesTitle();
16294       } else {
16295         DisplayTitle("");
16296       }
16297       continue;
16298     }
16299     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16300     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16301     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16302     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16303     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16304     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16305     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16306     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16307     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16308     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16309     if (IntFeature(&p, "done", &val, cps)) {
16310       FeatureDone(cps, val);
16311       continue;
16312     }
16313     /* Added by Tord: */
16314     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16315     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16316     /* End of additions by Tord */
16317
16318     /* [HGM] added features: */
16319     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16320     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16321     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16322     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16323     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16324     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16325     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16326     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16327         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16328         FREE(cps->option[cps->nrOptions].name);
16329         cps->option[cps->nrOptions].name = q; q = NULL;
16330         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16331           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16332             SendToProgram(buf, cps);
16333             continue;
16334         }
16335         if(cps->nrOptions >= MAX_OPTIONS) {
16336             cps->nrOptions--;
16337             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16338             DisplayError(buf, 0);
16339         }
16340         continue;
16341     }
16342     /* End of additions by HGM */
16343
16344     /* unknown feature: complain and skip */
16345     q = p;
16346     while (*q && *q != '=') q++;
16347     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16348     SendToProgram(buf, cps);
16349     p = q;
16350     if (*p == '=') {
16351       p++;
16352       if (*p == '\"') {
16353         p++;
16354         while (*p && *p != '\"') p++;
16355         if (*p == '\"') p++;
16356       } else {
16357         while (*p && *p != ' ') p++;
16358       }
16359     }
16360   }
16361
16362 }
16363
16364 void
16365 PeriodicUpdatesEvent (int newState)
16366 {
16367     if (newState == appData.periodicUpdates)
16368       return;
16369
16370     appData.periodicUpdates=newState;
16371
16372     /* Display type changes, so update it now */
16373 //    DisplayAnalysis();
16374
16375     /* Get the ball rolling again... */
16376     if (newState) {
16377         AnalysisPeriodicEvent(1);
16378         StartAnalysisClock();
16379     }
16380 }
16381
16382 void
16383 PonderNextMoveEvent (int newState)
16384 {
16385     if (newState == appData.ponderNextMove) return;
16386     if (gameMode == EditPosition) EditPositionDone(TRUE);
16387     if (newState) {
16388         SendToProgram("hard\n", &first);
16389         if (gameMode == TwoMachinesPlay) {
16390             SendToProgram("hard\n", &second);
16391         }
16392     } else {
16393         SendToProgram("easy\n", &first);
16394         thinkOutput[0] = NULLCHAR;
16395         if (gameMode == TwoMachinesPlay) {
16396             SendToProgram("easy\n", &second);
16397         }
16398     }
16399     appData.ponderNextMove = newState;
16400 }
16401
16402 void
16403 NewSettingEvent (int option, int *feature, char *command, int value)
16404 {
16405     char buf[MSG_SIZ];
16406
16407     if (gameMode == EditPosition) EditPositionDone(TRUE);
16408     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16409     if(feature == NULL || *feature) SendToProgram(buf, &first);
16410     if (gameMode == TwoMachinesPlay) {
16411         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16412     }
16413 }
16414
16415 void
16416 ShowThinkingEvent ()
16417 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16418 {
16419     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16420     int newState = appData.showThinking
16421         // [HGM] thinking: other features now need thinking output as well
16422         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16423
16424     if (oldState == newState) return;
16425     oldState = newState;
16426     if (gameMode == EditPosition) EditPositionDone(TRUE);
16427     if (oldState) {
16428         SendToProgram("post\n", &first);
16429         if (gameMode == TwoMachinesPlay) {
16430             SendToProgram("post\n", &second);
16431         }
16432     } else {
16433         SendToProgram("nopost\n", &first);
16434         thinkOutput[0] = NULLCHAR;
16435         if (gameMode == TwoMachinesPlay) {
16436             SendToProgram("nopost\n", &second);
16437         }
16438     }
16439 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16440 }
16441
16442 void
16443 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16444 {
16445   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16446   if (pr == NoProc) return;
16447   AskQuestion(title, question, replyPrefix, pr);
16448 }
16449
16450 void
16451 TypeInEvent (char firstChar)
16452 {
16453     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16454         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16455         gameMode == AnalyzeMode || gameMode == EditGame ||
16456         gameMode == EditPosition || gameMode == IcsExamining ||
16457         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16458         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16459                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16460                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16461         gameMode == Training) PopUpMoveDialog(firstChar);
16462 }
16463
16464 void
16465 TypeInDoneEvent (char *move)
16466 {
16467         Board board;
16468         int n, fromX, fromY, toX, toY;
16469         char promoChar;
16470         ChessMove moveType;
16471
16472         // [HGM] FENedit
16473         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16474                 EditPositionPasteFEN(move);
16475                 return;
16476         }
16477         // [HGM] movenum: allow move number to be typed in any mode
16478         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16479           ToNrEvent(2*n-1);
16480           return;
16481         }
16482         // undocumented kludge: allow command-line option to be typed in!
16483         // (potentially fatal, and does not implement the effect of the option.)
16484         // should only be used for options that are values on which future decisions will be made,
16485         // and definitely not on options that would be used during initialization.
16486         if(strstr(move, "!!! -") == move) {
16487             ParseArgsFromString(move+4);
16488             return;
16489         }
16490
16491       if (gameMode != EditGame && currentMove != forwardMostMove &&
16492         gameMode != Training) {
16493         DisplayMoveError(_("Displayed move is not current"));
16494       } else {
16495         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16496           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16497         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16498         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16499           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16500           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16501         } else {
16502           DisplayMoveError(_("Could not parse move"));
16503         }
16504       }
16505 }
16506
16507 void
16508 DisplayMove (int moveNumber)
16509 {
16510     char message[MSG_SIZ];
16511     char res[MSG_SIZ];
16512     char cpThinkOutput[MSG_SIZ];
16513
16514     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16515
16516     if (moveNumber == forwardMostMove - 1 ||
16517         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16518
16519         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16520
16521         if (strchr(cpThinkOutput, '\n')) {
16522             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16523         }
16524     } else {
16525         *cpThinkOutput = NULLCHAR;
16526     }
16527
16528     /* [AS] Hide thinking from human user */
16529     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16530         *cpThinkOutput = NULLCHAR;
16531         if( thinkOutput[0] != NULLCHAR ) {
16532             int i;
16533
16534             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16535                 cpThinkOutput[i] = '.';
16536             }
16537             cpThinkOutput[i] = NULLCHAR;
16538             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16539         }
16540     }
16541
16542     if (moveNumber == forwardMostMove - 1 &&
16543         gameInfo.resultDetails != NULL) {
16544         if (gameInfo.resultDetails[0] == NULLCHAR) {
16545           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16546         } else {
16547           snprintf(res, MSG_SIZ, " {%s} %s",
16548                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16549         }
16550     } else {
16551         res[0] = NULLCHAR;
16552     }
16553
16554     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16555         DisplayMessage(res, cpThinkOutput);
16556     } else {
16557       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16558                 WhiteOnMove(moveNumber) ? " " : ".. ",
16559                 parseList[moveNumber], res);
16560         DisplayMessage(message, cpThinkOutput);
16561     }
16562 }
16563
16564 void
16565 DisplayComment (int moveNumber, char *text)
16566 {
16567     char title[MSG_SIZ];
16568
16569     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16570       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16571     } else {
16572       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16573               WhiteOnMove(moveNumber) ? " " : ".. ",
16574               parseList[moveNumber]);
16575     }
16576     if (text != NULL && (appData.autoDisplayComment || commentUp))
16577         CommentPopUp(title, text);
16578 }
16579
16580 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16581  * might be busy thinking or pondering.  It can be omitted if your
16582  * gnuchess is configured to stop thinking immediately on any user
16583  * input.  However, that gnuchess feature depends on the FIONREAD
16584  * ioctl, which does not work properly on some flavors of Unix.
16585  */
16586 void
16587 Attention (ChessProgramState *cps)
16588 {
16589 #if ATTENTION
16590     if (!cps->useSigint) return;
16591     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16592     switch (gameMode) {
16593       case MachinePlaysWhite:
16594       case MachinePlaysBlack:
16595       case TwoMachinesPlay:
16596       case IcsPlayingWhite:
16597       case IcsPlayingBlack:
16598       case AnalyzeMode:
16599       case AnalyzeFile:
16600         /* Skip if we know it isn't thinking */
16601         if (!cps->maybeThinking) return;
16602         if (appData.debugMode)
16603           fprintf(debugFP, "Interrupting %s\n", cps->which);
16604         InterruptChildProcess(cps->pr);
16605         cps->maybeThinking = FALSE;
16606         break;
16607       default:
16608         break;
16609     }
16610 #endif /*ATTENTION*/
16611 }
16612
16613 int
16614 CheckFlags ()
16615 {
16616     if (whiteTimeRemaining <= 0) {
16617         if (!whiteFlag) {
16618             whiteFlag = TRUE;
16619             if (appData.icsActive) {
16620                 if (appData.autoCallFlag &&
16621                     gameMode == IcsPlayingBlack && !blackFlag) {
16622                   SendToICS(ics_prefix);
16623                   SendToICS("flag\n");
16624                 }
16625             } else {
16626                 if (blackFlag) {
16627                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16628                 } else {
16629                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16630                     if (appData.autoCallFlag) {
16631                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16632                         return TRUE;
16633                     }
16634                 }
16635             }
16636         }
16637     }
16638     if (blackTimeRemaining <= 0) {
16639         if (!blackFlag) {
16640             blackFlag = TRUE;
16641             if (appData.icsActive) {
16642                 if (appData.autoCallFlag &&
16643                     gameMode == IcsPlayingWhite && !whiteFlag) {
16644                   SendToICS(ics_prefix);
16645                   SendToICS("flag\n");
16646                 }
16647             } else {
16648                 if (whiteFlag) {
16649                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16650                 } else {
16651                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16652                     if (appData.autoCallFlag) {
16653                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16654                         return TRUE;
16655                     }
16656                 }
16657             }
16658         }
16659     }
16660     return FALSE;
16661 }
16662
16663 void
16664 CheckTimeControl ()
16665 {
16666     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16667         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16668
16669     /*
16670      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16671      */
16672     if ( !WhiteOnMove(forwardMostMove) ) {
16673         /* White made time control */
16674         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16675         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16676         /* [HGM] time odds: correct new time quota for time odds! */
16677                                             / WhitePlayer()->timeOdds;
16678         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16679     } else {
16680         lastBlack -= blackTimeRemaining;
16681         /* Black made time control */
16682         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16683                                             / WhitePlayer()->other->timeOdds;
16684         lastWhite = whiteTimeRemaining;
16685     }
16686 }
16687
16688 void
16689 DisplayBothClocks ()
16690 {
16691     int wom = gameMode == EditPosition ?
16692       !blackPlaysFirst : WhiteOnMove(currentMove);
16693     DisplayWhiteClock(whiteTimeRemaining, wom);
16694     DisplayBlackClock(blackTimeRemaining, !wom);
16695 }
16696
16697
16698 /* Timekeeping seems to be a portability nightmare.  I think everyone
16699    has ftime(), but I'm really not sure, so I'm including some ifdefs
16700    to use other calls if you don't.  Clocks will be less accurate if
16701    you have neither ftime nor gettimeofday.
16702 */
16703
16704 /* VS 2008 requires the #include outside of the function */
16705 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16706 #include <sys/timeb.h>
16707 #endif
16708
16709 /* Get the current time as a TimeMark */
16710 void
16711 GetTimeMark (TimeMark *tm)
16712 {
16713 #if HAVE_GETTIMEOFDAY
16714
16715     struct timeval timeVal;
16716     struct timezone timeZone;
16717
16718     gettimeofday(&timeVal, &timeZone);
16719     tm->sec = (long) timeVal.tv_sec;
16720     tm->ms = (int) (timeVal.tv_usec / 1000L);
16721
16722 #else /*!HAVE_GETTIMEOFDAY*/
16723 #if HAVE_FTIME
16724
16725 // include <sys/timeb.h> / moved to just above start of function
16726     struct timeb timeB;
16727
16728     ftime(&timeB);
16729     tm->sec = (long) timeB.time;
16730     tm->ms = (int) timeB.millitm;
16731
16732 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16733     tm->sec = (long) time(NULL);
16734     tm->ms = 0;
16735 #endif
16736 #endif
16737 }
16738
16739 /* Return the difference in milliseconds between two
16740    time marks.  We assume the difference will fit in a long!
16741 */
16742 long
16743 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16744 {
16745     return 1000L*(tm2->sec - tm1->sec) +
16746            (long) (tm2->ms - tm1->ms);
16747 }
16748
16749
16750 /*
16751  * Code to manage the game clocks.
16752  *
16753  * In tournament play, black starts the clock and then white makes a move.
16754  * We give the human user a slight advantage if he is playing white---the
16755  * clocks don't run until he makes his first move, so it takes zero time.
16756  * Also, we don't account for network lag, so we could get out of sync
16757  * with GNU Chess's clock -- but then, referees are always right.
16758  */
16759
16760 static TimeMark tickStartTM;
16761 static long intendedTickLength;
16762
16763 long
16764 NextTickLength (long timeRemaining)
16765 {
16766     long nominalTickLength, nextTickLength;
16767
16768     if (timeRemaining > 0L && timeRemaining <= 10000L)
16769       nominalTickLength = 100L;
16770     else
16771       nominalTickLength = 1000L;
16772     nextTickLength = timeRemaining % nominalTickLength;
16773     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16774
16775     return nextTickLength;
16776 }
16777
16778 /* Adjust clock one minute up or down */
16779 void
16780 AdjustClock (Boolean which, int dir)
16781 {
16782     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16783     if(which) blackTimeRemaining += 60000*dir;
16784     else      whiteTimeRemaining += 60000*dir;
16785     DisplayBothClocks();
16786     adjustedClock = TRUE;
16787 }
16788
16789 /* Stop clocks and reset to a fresh time control */
16790 void
16791 ResetClocks ()
16792 {
16793     (void) StopClockTimer();
16794     if (appData.icsActive) {
16795         whiteTimeRemaining = blackTimeRemaining = 0;
16796     } else if (searchTime) {
16797         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16798         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16799     } else { /* [HGM] correct new time quote for time odds */
16800         whiteTC = blackTC = fullTimeControlString;
16801         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16802         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16803     }
16804     if (whiteFlag || blackFlag) {
16805         DisplayTitle("");
16806         whiteFlag = blackFlag = FALSE;
16807     }
16808     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16809     DisplayBothClocks();
16810     adjustedClock = FALSE;
16811 }
16812
16813 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16814
16815 /* Decrement running clock by amount of time that has passed */
16816 void
16817 DecrementClocks ()
16818 {
16819     long timeRemaining;
16820     long lastTickLength, fudge;
16821     TimeMark now;
16822
16823     if (!appData.clockMode) return;
16824     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16825
16826     GetTimeMark(&now);
16827
16828     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16829
16830     /* Fudge if we woke up a little too soon */
16831     fudge = intendedTickLength - lastTickLength;
16832     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16833
16834     if (WhiteOnMove(forwardMostMove)) {
16835         if(whiteNPS >= 0) lastTickLength = 0;
16836         timeRemaining = whiteTimeRemaining -= lastTickLength;
16837         if(timeRemaining < 0 && !appData.icsActive) {
16838             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16839             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16840                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16841                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16842             }
16843         }
16844         DisplayWhiteClock(whiteTimeRemaining - fudge,
16845                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16846     } else {
16847         if(blackNPS >= 0) lastTickLength = 0;
16848         timeRemaining = blackTimeRemaining -= lastTickLength;
16849         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16850             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16851             if(suddenDeath) {
16852                 blackStartMove = forwardMostMove;
16853                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16854             }
16855         }
16856         DisplayBlackClock(blackTimeRemaining - fudge,
16857                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16858     }
16859     if (CheckFlags()) return;
16860
16861     if(twoBoards) { // count down secondary board's clocks as well
16862         activePartnerTime -= lastTickLength;
16863         partnerUp = 1;
16864         if(activePartner == 'W')
16865             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16866         else
16867             DisplayBlackClock(activePartnerTime, TRUE);
16868         partnerUp = 0;
16869     }
16870
16871     tickStartTM = now;
16872     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16873     StartClockTimer(intendedTickLength);
16874
16875     /* if the time remaining has fallen below the alarm threshold, sound the
16876      * alarm. if the alarm has sounded and (due to a takeback or time control
16877      * with increment) the time remaining has increased to a level above the
16878      * threshold, reset the alarm so it can sound again.
16879      */
16880
16881     if (appData.icsActive && appData.icsAlarm) {
16882
16883         /* make sure we are dealing with the user's clock */
16884         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16885                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16886            )) return;
16887
16888         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16889             alarmSounded = FALSE;
16890         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16891             PlayAlarmSound();
16892             alarmSounded = TRUE;
16893         }
16894     }
16895 }
16896
16897
16898 /* A player has just moved, so stop the previously running
16899    clock and (if in clock mode) start the other one.
16900    We redisplay both clocks in case we're in ICS mode, because
16901    ICS gives us an update to both clocks after every move.
16902    Note that this routine is called *after* forwardMostMove
16903    is updated, so the last fractional tick must be subtracted
16904    from the color that is *not* on move now.
16905 */
16906 void
16907 SwitchClocks (int newMoveNr)
16908 {
16909     long lastTickLength;
16910     TimeMark now;
16911     int flagged = FALSE;
16912
16913     GetTimeMark(&now);
16914
16915     if (StopClockTimer() && appData.clockMode) {
16916         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16917         if (!WhiteOnMove(forwardMostMove)) {
16918             if(blackNPS >= 0) lastTickLength = 0;
16919             blackTimeRemaining -= lastTickLength;
16920            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16921 //         if(pvInfoList[forwardMostMove].time == -1)
16922                  pvInfoList[forwardMostMove].time =               // use GUI time
16923                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16924         } else {
16925            if(whiteNPS >= 0) lastTickLength = 0;
16926            whiteTimeRemaining -= lastTickLength;
16927            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16928 //         if(pvInfoList[forwardMostMove].time == -1)
16929                  pvInfoList[forwardMostMove].time =
16930                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16931         }
16932         flagged = CheckFlags();
16933     }
16934     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16935     CheckTimeControl();
16936
16937     if (flagged || !appData.clockMode) return;
16938
16939     switch (gameMode) {
16940       case MachinePlaysBlack:
16941       case MachinePlaysWhite:
16942       case BeginningOfGame:
16943         if (pausing) return;
16944         break;
16945
16946       case EditGame:
16947       case PlayFromGameFile:
16948       case IcsExamining:
16949         return;
16950
16951       default:
16952         break;
16953     }
16954
16955     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16956         if(WhiteOnMove(forwardMostMove))
16957              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16958         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16959     }
16960
16961     tickStartTM = now;
16962     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16963       whiteTimeRemaining : blackTimeRemaining);
16964     StartClockTimer(intendedTickLength);
16965 }
16966
16967
16968 /* Stop both clocks */
16969 void
16970 StopClocks ()
16971 {
16972     long lastTickLength;
16973     TimeMark now;
16974
16975     if (!StopClockTimer()) return;
16976     if (!appData.clockMode) return;
16977
16978     GetTimeMark(&now);
16979
16980     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16981     if (WhiteOnMove(forwardMostMove)) {
16982         if(whiteNPS >= 0) lastTickLength = 0;
16983         whiteTimeRemaining -= lastTickLength;
16984         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16985     } else {
16986         if(blackNPS >= 0) lastTickLength = 0;
16987         blackTimeRemaining -= lastTickLength;
16988         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16989     }
16990     CheckFlags();
16991 }
16992
16993 /* Start clock of player on move.  Time may have been reset, so
16994    if clock is already running, stop and restart it. */
16995 void
16996 StartClocks ()
16997 {
16998     (void) StopClockTimer(); /* in case it was running already */
16999     DisplayBothClocks();
17000     if (CheckFlags()) return;
17001
17002     if (!appData.clockMode) return;
17003     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17004
17005     GetTimeMark(&tickStartTM);
17006     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17007       whiteTimeRemaining : blackTimeRemaining);
17008
17009    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17010     whiteNPS = blackNPS = -1;
17011     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17012        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17013         whiteNPS = first.nps;
17014     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17015        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17016         blackNPS = first.nps;
17017     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17018         whiteNPS = second.nps;
17019     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17020         blackNPS = second.nps;
17021     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17022
17023     StartClockTimer(intendedTickLength);
17024 }
17025
17026 char *
17027 TimeString (long ms)
17028 {
17029     long second, minute, hour, day;
17030     char *sign = "";
17031     static char buf[32];
17032
17033     if (ms > 0 && ms <= 9900) {
17034       /* convert milliseconds to tenths, rounding up */
17035       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17036
17037       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17038       return buf;
17039     }
17040
17041     /* convert milliseconds to seconds, rounding up */
17042     /* use floating point to avoid strangeness of integer division
17043        with negative dividends on many machines */
17044     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17045
17046     if (second < 0) {
17047         sign = "-";
17048         second = -second;
17049     }
17050
17051     day = second / (60 * 60 * 24);
17052     second = second % (60 * 60 * 24);
17053     hour = second / (60 * 60);
17054     second = second % (60 * 60);
17055     minute = second / 60;
17056     second = second % 60;
17057
17058     if (day > 0)
17059       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17060               sign, day, hour, minute, second);
17061     else if (hour > 0)
17062       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17063     else
17064       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17065
17066     return buf;
17067 }
17068
17069
17070 /*
17071  * This is necessary because some C libraries aren't ANSI C compliant yet.
17072  */
17073 char *
17074 StrStr (char *string, char *match)
17075 {
17076     int i, length;
17077
17078     length = strlen(match);
17079
17080     for (i = strlen(string) - length; i >= 0; i--, string++)
17081       if (!strncmp(match, string, length))
17082         return string;
17083
17084     return NULL;
17085 }
17086
17087 char *
17088 StrCaseStr (char *string, char *match)
17089 {
17090     int i, j, length;
17091
17092     length = strlen(match);
17093
17094     for (i = strlen(string) - length; i >= 0; i--, string++) {
17095         for (j = 0; j < length; j++) {
17096             if (ToLower(match[j]) != ToLower(string[j]))
17097               break;
17098         }
17099         if (j == length) return string;
17100     }
17101
17102     return NULL;
17103 }
17104
17105 #ifndef _amigados
17106 int
17107 StrCaseCmp (char *s1, char *s2)
17108 {
17109     char c1, c2;
17110
17111     for (;;) {
17112         c1 = ToLower(*s1++);
17113         c2 = ToLower(*s2++);
17114         if (c1 > c2) return 1;
17115         if (c1 < c2) return -1;
17116         if (c1 == NULLCHAR) return 0;
17117     }
17118 }
17119
17120
17121 int
17122 ToLower (int c)
17123 {
17124     return isupper(c) ? tolower(c) : c;
17125 }
17126
17127
17128 int
17129 ToUpper (int c)
17130 {
17131     return islower(c) ? toupper(c) : c;
17132 }
17133 #endif /* !_amigados    */
17134
17135 char *
17136 StrSave (char *s)
17137 {
17138   char *ret;
17139
17140   if ((ret = (char *) malloc(strlen(s) + 1)))
17141     {
17142       safeStrCpy(ret, s, strlen(s)+1);
17143     }
17144   return ret;
17145 }
17146
17147 char *
17148 StrSavePtr (char *s, char **savePtr)
17149 {
17150     if (*savePtr) {
17151         free(*savePtr);
17152     }
17153     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17154       safeStrCpy(*savePtr, s, strlen(s)+1);
17155     }
17156     return(*savePtr);
17157 }
17158
17159 char *
17160 PGNDate ()
17161 {
17162     time_t clock;
17163     struct tm *tm;
17164     char buf[MSG_SIZ];
17165
17166     clock = time((time_t *)NULL);
17167     tm = localtime(&clock);
17168     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17169             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17170     return StrSave(buf);
17171 }
17172
17173
17174 char *
17175 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17176 {
17177     int i, j, fromX, fromY, toX, toY;
17178     int whiteToPlay;
17179     char buf[MSG_SIZ];
17180     char *p, *q;
17181     int emptycount;
17182     ChessSquare piece;
17183
17184     whiteToPlay = (gameMode == EditPosition) ?
17185       !blackPlaysFirst : (move % 2 == 0);
17186     p = buf;
17187
17188     /* Piece placement data */
17189     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17190         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17191         emptycount = 0;
17192         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17193             if (boards[move][i][j] == EmptySquare) {
17194                 emptycount++;
17195             } else { ChessSquare piece = boards[move][i][j];
17196                 if (emptycount > 0) {
17197                     if(emptycount<10) /* [HGM] can be >= 10 */
17198                         *p++ = '0' + emptycount;
17199                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17200                     emptycount = 0;
17201                 }
17202                 if(PieceToChar(piece) == '+') {
17203                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17204                     *p++ = '+';
17205                     piece = (ChessSquare)(DEMOTED piece);
17206                 }
17207                 *p++ = PieceToChar(piece);
17208                 if(p[-1] == '~') {
17209                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17210                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17211                     *p++ = '~';
17212                 }
17213             }
17214         }
17215         if (emptycount > 0) {
17216             if(emptycount<10) /* [HGM] can be >= 10 */
17217                 *p++ = '0' + emptycount;
17218             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17219             emptycount = 0;
17220         }
17221         *p++ = '/';
17222     }
17223     *(p - 1) = ' ';
17224
17225     /* [HGM] print Crazyhouse or Shogi holdings */
17226     if( gameInfo.holdingsWidth ) {
17227         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17228         q = p;
17229         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17230             piece = boards[move][i][BOARD_WIDTH-1];
17231             if( piece != EmptySquare )
17232               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17233                   *p++ = PieceToChar(piece);
17234         }
17235         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17236             piece = boards[move][BOARD_HEIGHT-i-1][0];
17237             if( piece != EmptySquare )
17238               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17239                   *p++ = PieceToChar(piece);
17240         }
17241
17242         if( q == p ) *p++ = '-';
17243         *p++ = ']';
17244         *p++ = ' ';
17245     }
17246
17247     /* Active color */
17248     *p++ = whiteToPlay ? 'w' : 'b';
17249     *p++ = ' ';
17250
17251   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17252     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17253   } else {
17254   if(nrCastlingRights) {
17255      q = p;
17256      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17257        /* [HGM] write directly from rights */
17258            if(boards[move][CASTLING][2] != NoRights &&
17259               boards[move][CASTLING][0] != NoRights   )
17260                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17261            if(boards[move][CASTLING][2] != NoRights &&
17262               boards[move][CASTLING][1] != NoRights   )
17263                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17264            if(boards[move][CASTLING][5] != NoRights &&
17265               boards[move][CASTLING][3] != NoRights   )
17266                 *p++ = boards[move][CASTLING][3] + AAA;
17267            if(boards[move][CASTLING][5] != NoRights &&
17268               boards[move][CASTLING][4] != NoRights   )
17269                 *p++ = boards[move][CASTLING][4] + AAA;
17270      } else {
17271
17272         /* [HGM] write true castling rights */
17273         if( nrCastlingRights == 6 ) {
17274             int q, k=0;
17275             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17276                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17277             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17278                  boards[move][CASTLING][2] != NoRights  );
17279             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17280                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17281                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17282                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17283                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17284             }
17285             if(q) *p++ = 'Q';
17286             k = 0;
17287             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17288                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17289             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17290                  boards[move][CASTLING][5] != NoRights  );
17291             if(gameInfo.variant == VariantSChess) {
17292                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17293                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17294                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17295                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17296             }
17297             if(q) *p++ = 'q';
17298         }
17299      }
17300      if (q == p) *p++ = '-'; /* No castling rights */
17301      *p++ = ' ';
17302   }
17303
17304   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17305      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17306      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17307     /* En passant target square */
17308     if (move > backwardMostMove) {
17309         fromX = moveList[move - 1][0] - AAA;
17310         fromY = moveList[move - 1][1] - ONE;
17311         toX = moveList[move - 1][2] - AAA;
17312         toY = moveList[move - 1][3] - ONE;
17313         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17314             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17315             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17316             fromX == toX) {
17317             /* 2-square pawn move just happened */
17318             *p++ = toX + AAA;
17319             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17320         } else {
17321             *p++ = '-';
17322         }
17323     } else if(move == backwardMostMove) {
17324         // [HGM] perhaps we should always do it like this, and forget the above?
17325         if((signed char)boards[move][EP_STATUS] >= 0) {
17326             *p++ = boards[move][EP_STATUS] + AAA;
17327             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17328         } else {
17329             *p++ = '-';
17330         }
17331     } else {
17332         *p++ = '-';
17333     }
17334     *p++ = ' ';
17335   }
17336   }
17337
17338     if(moveCounts)
17339     {   int i = 0, j=move;
17340
17341         /* [HGM] find reversible plies */
17342         if (appData.debugMode) { int k;
17343             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17344             for(k=backwardMostMove; k<=forwardMostMove; k++)
17345                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17346
17347         }
17348
17349         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17350         if( j == backwardMostMove ) i += initialRulePlies;
17351         sprintf(p, "%d ", i);
17352         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17353
17354         /* Fullmove number */
17355         sprintf(p, "%d", (move / 2) + 1);
17356     } else *--p = NULLCHAR;
17357
17358     return StrSave(buf);
17359 }
17360
17361 Boolean
17362 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17363 {
17364     int i, j;
17365     char *p, c;
17366     int emptycount, virgin[BOARD_FILES];
17367     ChessSquare piece;
17368
17369     p = fen;
17370
17371     /* [HGM] by default clear Crazyhouse holdings, if present */
17372     if(gameInfo.holdingsWidth) {
17373        for(i=0; i<BOARD_HEIGHT; i++) {
17374            board[i][0]             = EmptySquare; /* black holdings */
17375            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17376            board[i][1]             = (ChessSquare) 0; /* black counts */
17377            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17378        }
17379     }
17380
17381     /* Piece placement data */
17382     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17383         j = 0;
17384         for (;;) {
17385             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17386                 if (*p == '/') p++;
17387                 emptycount = gameInfo.boardWidth - j;
17388                 while (emptycount--)
17389                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17390                 break;
17391 #if(BOARD_FILES >= 10)
17392             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17393                 p++; emptycount=10;
17394                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17395                 while (emptycount--)
17396                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17397 #endif
17398             } else if (isdigit(*p)) {
17399                 emptycount = *p++ - '0';
17400                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17401                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17402                 while (emptycount--)
17403                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17404             } else if (*p == '+' || isalpha(*p)) {
17405                 if (j >= gameInfo.boardWidth) return FALSE;
17406                 if(*p=='+') {
17407                     piece = CharToPiece(*++p);
17408                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17409                     piece = (ChessSquare) (PROMOTED piece ); p++;
17410                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17411                 } else piece = CharToPiece(*p++);
17412
17413                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17414                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17415                     piece = (ChessSquare) (PROMOTED piece);
17416                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17417                     p++;
17418                 }
17419                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17420             } else {
17421                 return FALSE;
17422             }
17423         }
17424     }
17425     while (*p == '/' || *p == ' ') p++;
17426
17427     /* [HGM] look for Crazyhouse holdings here */
17428     while(*p==' ') p++;
17429     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17430         if(*p == '[') p++;
17431         if(*p == '-' ) p++; /* empty holdings */ else {
17432             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17433             /* if we would allow FEN reading to set board size, we would   */
17434             /* have to add holdings and shift the board read so far here   */
17435             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17436                 p++;
17437                 if((int) piece >= (int) BlackPawn ) {
17438                     i = (int)piece - (int)BlackPawn;
17439                     i = PieceToNumber((ChessSquare)i);
17440                     if( i >= gameInfo.holdingsSize ) return FALSE;
17441                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17442                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17443                 } else {
17444                     i = (int)piece - (int)WhitePawn;
17445                     i = PieceToNumber((ChessSquare)i);
17446                     if( i >= gameInfo.holdingsSize ) return FALSE;
17447                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17448                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17449                 }
17450             }
17451         }
17452         if(*p == ']') p++;
17453     }
17454
17455     while(*p == ' ') p++;
17456
17457     /* Active color */
17458     c = *p++;
17459     if(appData.colorNickNames) {
17460       if( c == appData.colorNickNames[0] ) c = 'w'; else
17461       if( c == appData.colorNickNames[1] ) c = 'b';
17462     }
17463     switch (c) {
17464       case 'w':
17465         *blackPlaysFirst = FALSE;
17466         break;
17467       case 'b':
17468         *blackPlaysFirst = TRUE;
17469         break;
17470       default:
17471         return FALSE;
17472     }
17473
17474     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17475     /* return the extra info in global variiables             */
17476
17477     /* set defaults in case FEN is incomplete */
17478     board[EP_STATUS] = EP_UNKNOWN;
17479     for(i=0; i<nrCastlingRights; i++ ) {
17480         board[CASTLING][i] =
17481             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17482     }   /* assume possible unless obviously impossible */
17483     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17484     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17485     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17486                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17487     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17488     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17489     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17490                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17491     FENrulePlies = 0;
17492
17493     while(*p==' ') p++;
17494     if(nrCastlingRights) {
17495       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17496       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17497           /* castling indicator present, so default becomes no castlings */
17498           for(i=0; i<nrCastlingRights; i++ ) {
17499                  board[CASTLING][i] = NoRights;
17500           }
17501       }
17502       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17503              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17504              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17505              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17506         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17507
17508         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17509             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17510             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17511         }
17512         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17513             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17514         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17515                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17516         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17517                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17518         switch(c) {
17519           case'K':
17520               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17521               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17522               board[CASTLING][2] = whiteKingFile;
17523               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17524               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17525               break;
17526           case'Q':
17527               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17528               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17529               board[CASTLING][2] = whiteKingFile;
17530               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17531               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17532               break;
17533           case'k':
17534               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17535               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17536               board[CASTLING][5] = blackKingFile;
17537               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17538               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17539               break;
17540           case'q':
17541               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17542               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17543               board[CASTLING][5] = blackKingFile;
17544               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17545               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17546           case '-':
17547               break;
17548           default: /* FRC castlings */
17549               if(c >= 'a') { /* black rights */
17550                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17551                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17552                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17553                   if(i == BOARD_RGHT) break;
17554                   board[CASTLING][5] = i;
17555                   c -= AAA;
17556                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17557                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17558                   if(c > i)
17559                       board[CASTLING][3] = c;
17560                   else
17561                       board[CASTLING][4] = c;
17562               } else { /* white rights */
17563                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17564                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17565                     if(board[0][i] == WhiteKing) break;
17566                   if(i == BOARD_RGHT) break;
17567                   board[CASTLING][2] = i;
17568                   c -= AAA - 'a' + 'A';
17569                   if(board[0][c] >= WhiteKing) break;
17570                   if(c > i)
17571                       board[CASTLING][0] = c;
17572                   else
17573                       board[CASTLING][1] = c;
17574               }
17575         }
17576       }
17577       for(i=0; i<nrCastlingRights; i++)
17578         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17579       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17580     if (appData.debugMode) {
17581         fprintf(debugFP, "FEN castling rights:");
17582         for(i=0; i<nrCastlingRights; i++)
17583         fprintf(debugFP, " %d", board[CASTLING][i]);
17584         fprintf(debugFP, "\n");
17585     }
17586
17587       while(*p==' ') p++;
17588     }
17589
17590     /* read e.p. field in games that know e.p. capture */
17591     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17592        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17593        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17594       if(*p=='-') {
17595         p++; board[EP_STATUS] = EP_NONE;
17596       } else {
17597          char c = *p++ - AAA;
17598
17599          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17600          if(*p >= '0' && *p <='9') p++;
17601          board[EP_STATUS] = c;
17602       }
17603     }
17604
17605
17606     if(sscanf(p, "%d", &i) == 1) {
17607         FENrulePlies = i; /* 50-move ply counter */
17608         /* (The move number is still ignored)    */
17609     }
17610
17611     return TRUE;
17612 }
17613
17614 void
17615 EditPositionPasteFEN (char *fen)
17616 {
17617   if (fen != NULL) {
17618     Board initial_position;
17619
17620     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17621       DisplayError(_("Bad FEN position in clipboard"), 0);
17622       return ;
17623     } else {
17624       int savedBlackPlaysFirst = blackPlaysFirst;
17625       EditPositionEvent();
17626       blackPlaysFirst = savedBlackPlaysFirst;
17627       CopyBoard(boards[0], initial_position);
17628       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17629       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17630       DisplayBothClocks();
17631       DrawPosition(FALSE, boards[currentMove]);
17632     }
17633   }
17634 }
17635
17636 static char cseq[12] = "\\   ";
17637
17638 Boolean
17639 set_cont_sequence (char *new_seq)
17640 {
17641     int len;
17642     Boolean ret;
17643
17644     // handle bad attempts to set the sequence
17645         if (!new_seq)
17646                 return 0; // acceptable error - no debug
17647
17648     len = strlen(new_seq);
17649     ret = (len > 0) && (len < sizeof(cseq));
17650     if (ret)
17651       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17652     else if (appData.debugMode)
17653       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17654     return ret;
17655 }
17656
17657 /*
17658     reformat a source message so words don't cross the width boundary.  internal
17659     newlines are not removed.  returns the wrapped size (no null character unless
17660     included in source message).  If dest is NULL, only calculate the size required
17661     for the dest buffer.  lp argument indicats line position upon entry, and it's
17662     passed back upon exit.
17663 */
17664 int
17665 wrap (char *dest, char *src, int count, int width, int *lp)
17666 {
17667     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17668
17669     cseq_len = strlen(cseq);
17670     old_line = line = *lp;
17671     ansi = len = clen = 0;
17672
17673     for (i=0; i < count; i++)
17674     {
17675         if (src[i] == '\033')
17676             ansi = 1;
17677
17678         // if we hit the width, back up
17679         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17680         {
17681             // store i & len in case the word is too long
17682             old_i = i, old_len = len;
17683
17684             // find the end of the last word
17685             while (i && src[i] != ' ' && src[i] != '\n')
17686             {
17687                 i--;
17688                 len--;
17689             }
17690
17691             // word too long?  restore i & len before splitting it
17692             if ((old_i-i+clen) >= width)
17693             {
17694                 i = old_i;
17695                 len = old_len;
17696             }
17697
17698             // extra space?
17699             if (i && src[i-1] == ' ')
17700                 len--;
17701
17702             if (src[i] != ' ' && src[i] != '\n')
17703             {
17704                 i--;
17705                 if (len)
17706                     len--;
17707             }
17708
17709             // now append the newline and continuation sequence
17710             if (dest)
17711                 dest[len] = '\n';
17712             len++;
17713             if (dest)
17714                 strncpy(dest+len, cseq, cseq_len);
17715             len += cseq_len;
17716             line = cseq_len;
17717             clen = cseq_len;
17718             continue;
17719         }
17720
17721         if (dest)
17722             dest[len] = src[i];
17723         len++;
17724         if (!ansi)
17725             line++;
17726         if (src[i] == '\n')
17727             line = 0;
17728         if (src[i] == 'm')
17729             ansi = 0;
17730     }
17731     if (dest && appData.debugMode)
17732     {
17733         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17734             count, width, line, len, *lp);
17735         show_bytes(debugFP, src, count);
17736         fprintf(debugFP, "\ndest: ");
17737         show_bytes(debugFP, dest, len);
17738         fprintf(debugFP, "\n");
17739     }
17740     *lp = dest ? line : old_line;
17741
17742     return len;
17743 }
17744
17745 // [HGM] vari: routines for shelving variations
17746 Boolean modeRestore = FALSE;
17747
17748 void
17749 PushInner (int firstMove, int lastMove)
17750 {
17751         int i, j, nrMoves = lastMove - firstMove;
17752
17753         // push current tail of game on stack
17754         savedResult[storedGames] = gameInfo.result;
17755         savedDetails[storedGames] = gameInfo.resultDetails;
17756         gameInfo.resultDetails = NULL;
17757         savedFirst[storedGames] = firstMove;
17758         savedLast [storedGames] = lastMove;
17759         savedFramePtr[storedGames] = framePtr;
17760         framePtr -= nrMoves; // reserve space for the boards
17761         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17762             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17763             for(j=0; j<MOVE_LEN; j++)
17764                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17765             for(j=0; j<2*MOVE_LEN; j++)
17766                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17767             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17768             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17769             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17770             pvInfoList[firstMove+i-1].depth = 0;
17771             commentList[framePtr+i] = commentList[firstMove+i];
17772             commentList[firstMove+i] = NULL;
17773         }
17774
17775         storedGames++;
17776         forwardMostMove = firstMove; // truncate game so we can start variation
17777 }
17778
17779 void
17780 PushTail (int firstMove, int lastMove)
17781 {
17782         if(appData.icsActive) { // only in local mode
17783                 forwardMostMove = currentMove; // mimic old ICS behavior
17784                 return;
17785         }
17786         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17787
17788         PushInner(firstMove, lastMove);
17789         if(storedGames == 1) GreyRevert(FALSE);
17790         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17791 }
17792
17793 void
17794 PopInner (Boolean annotate)
17795 {
17796         int i, j, nrMoves;
17797         char buf[8000], moveBuf[20];
17798
17799         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17800         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17801         nrMoves = savedLast[storedGames] - currentMove;
17802         if(annotate) {
17803                 int cnt = 10;
17804                 if(!WhiteOnMove(currentMove))
17805                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17806                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17807                 for(i=currentMove; i<forwardMostMove; i++) {
17808                         if(WhiteOnMove(i))
17809                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17810                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17811                         strcat(buf, moveBuf);
17812                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17813                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17814                 }
17815                 strcat(buf, ")");
17816         }
17817         for(i=1; i<=nrMoves; i++) { // copy last variation back
17818             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17819             for(j=0; j<MOVE_LEN; j++)
17820                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17821             for(j=0; j<2*MOVE_LEN; j++)
17822                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17823             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17824             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17825             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17826             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17827             commentList[currentMove+i] = commentList[framePtr+i];
17828             commentList[framePtr+i] = NULL;
17829         }
17830         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17831         framePtr = savedFramePtr[storedGames];
17832         gameInfo.result = savedResult[storedGames];
17833         if(gameInfo.resultDetails != NULL) {
17834             free(gameInfo.resultDetails);
17835       }
17836         gameInfo.resultDetails = savedDetails[storedGames];
17837         forwardMostMove = currentMove + nrMoves;
17838 }
17839
17840 Boolean
17841 PopTail (Boolean annotate)
17842 {
17843         if(appData.icsActive) return FALSE; // only in local mode
17844         if(!storedGames) return FALSE; // sanity
17845         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17846
17847         PopInner(annotate);
17848         if(currentMove < forwardMostMove) ForwardEvent(); else
17849         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17850
17851         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17852         return TRUE;
17853 }
17854
17855 void
17856 CleanupTail ()
17857 {       // remove all shelved variations
17858         int i;
17859         for(i=0; i<storedGames; i++) {
17860             if(savedDetails[i])
17861                 free(savedDetails[i]);
17862             savedDetails[i] = NULL;
17863         }
17864         for(i=framePtr; i<MAX_MOVES; i++) {
17865                 if(commentList[i]) free(commentList[i]);
17866                 commentList[i] = NULL;
17867         }
17868         framePtr = MAX_MOVES-1;
17869         storedGames = 0;
17870 }
17871
17872 void
17873 LoadVariation (int index, char *text)
17874 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17875         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17876         int level = 0, move;
17877
17878         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17879         // first find outermost bracketing variation
17880         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17881             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17882                 if(*p == '{') wait = '}'; else
17883                 if(*p == '[') wait = ']'; else
17884                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17885                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17886             }
17887             if(*p == wait) wait = NULLCHAR; // closing ]} found
17888             p++;
17889         }
17890         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17891         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17892         end[1] = NULLCHAR; // clip off comment beyond variation
17893         ToNrEvent(currentMove-1);
17894         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17895         // kludge: use ParsePV() to append variation to game
17896         move = currentMove;
17897         ParsePV(start, TRUE, TRUE);
17898         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17899         ClearPremoveHighlights();
17900         CommentPopDown();
17901         ToNrEvent(currentMove+1);
17902 }
17903
17904 void
17905 LoadTheme ()
17906 {
17907     char *p, *q, buf[MSG_SIZ];
17908     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17909         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17910         ParseArgsFromString(buf);
17911         ActivateTheme(TRUE); // also redo colors
17912         return;
17913     }
17914     p = nickName;
17915     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17916     {
17917         int len;
17918         q = appData.themeNames;
17919         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17920       if(appData.useBitmaps) {
17921         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17922                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17923                 appData.liteBackTextureMode,
17924                 appData.darkBackTextureMode );
17925       } else {
17926         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17927                 Col2Text(2),   // lightSquareColor
17928                 Col2Text(3) ); // darkSquareColor
17929       }
17930       if(appData.useBorder) {
17931         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17932                 appData.border);
17933       } else {
17934         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17935       }
17936       if(appData.useFont) {
17937         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17938                 appData.renderPiecesWithFont,
17939                 appData.fontToPieceTable,
17940                 Col2Text(9),    // appData.fontBackColorWhite
17941                 Col2Text(10) ); // appData.fontForeColorBlack
17942       } else {
17943         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17944                 appData.pieceDirectory);
17945         if(!appData.pieceDirectory[0])
17946           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17947                 Col2Text(0),   // whitePieceColor
17948                 Col2Text(1) ); // blackPieceColor
17949       }
17950       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17951                 Col2Text(4),   // highlightSquareColor
17952                 Col2Text(5) ); // premoveHighlightColor
17953         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17954         if(insert != q) insert[-1] = NULLCHAR;
17955         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17956         if(q)   free(q);
17957     }
17958     ActivateTheme(FALSE);
17959 }