1a3014877f85513d5a6abbdf356de8bd5ba6de7d
[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 *variantNames[] = VARIANT_NAMES;
2010 char *
2011 VariantName (VariantClass v)
2012 {
2013     return variantNames[v];
2014 }
2015
2016
2017 /* Identify a variant from the strings the chess servers use or the
2018    PGN Variant tag names we use. */
2019 VariantClass
2020 StringToVariant (char *e)
2021 {
2022     char *p;
2023     int wnum = -1;
2024     VariantClass v = VariantNormal;
2025     int i, found = FALSE;
2026     char buf[MSG_SIZ];
2027     int len;
2028
2029     if (!e) return v;
2030
2031     /* [HGM] skip over optional board-size prefixes */
2032     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2033         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2034         while( *e++ != '_');
2035     }
2036
2037     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2038         v = VariantNormal;
2039         found = TRUE;
2040     } else
2041     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2042       if (StrCaseStr(e, variantNames[i])) {
2043         v = (VariantClass) i;
2044         found = TRUE;
2045         break;
2046       }
2047     }
2048
2049     if (!found) {
2050       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2051           || StrCaseStr(e, "wild/fr")
2052           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2053         v = VariantFischeRandom;
2054       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2055                  (i = 1, p = StrCaseStr(e, "w"))) {
2056         p += i;
2057         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2058         if (isdigit(*p)) {
2059           wnum = atoi(p);
2060         } else {
2061           wnum = -1;
2062         }
2063         switch (wnum) {
2064         case 0: /* FICS only, actually */
2065         case 1:
2066           /* Castling legal even if K starts on d-file */
2067           v = VariantWildCastle;
2068           break;
2069         case 2:
2070         case 3:
2071         case 4:
2072           /* Castling illegal even if K & R happen to start in
2073              normal positions. */
2074           v = VariantNoCastle;
2075           break;
2076         case 5:
2077         case 7:
2078         case 8:
2079         case 10:
2080         case 11:
2081         case 12:
2082         case 13:
2083         case 14:
2084         case 15:
2085         case 18:
2086         case 19:
2087           /* Castling legal iff K & R start in normal positions */
2088           v = VariantNormal;
2089           break;
2090         case 6:
2091         case 20:
2092         case 21:
2093           /* Special wilds for position setup; unclear what to do here */
2094           v = VariantLoadable;
2095           break;
2096         case 9:
2097           /* Bizarre ICC game */
2098           v = VariantTwoKings;
2099           break;
2100         case 16:
2101           v = VariantKriegspiel;
2102           break;
2103         case 17:
2104           v = VariantLosers;
2105           break;
2106         case 22:
2107           v = VariantFischeRandom;
2108           break;
2109         case 23:
2110           v = VariantCrazyhouse;
2111           break;
2112         case 24:
2113           v = VariantBughouse;
2114           break;
2115         case 25:
2116           v = Variant3Check;
2117           break;
2118         case 26:
2119           /* Not quite the same as FICS suicide! */
2120           v = VariantGiveaway;
2121           break;
2122         case 27:
2123           v = VariantAtomic;
2124           break;
2125         case 28:
2126           v = VariantShatranj;
2127           break;
2128
2129         /* Temporary names for future ICC types.  The name *will* change in
2130            the next xboard/WinBoard release after ICC defines it. */
2131         case 29:
2132           v = Variant29;
2133           break;
2134         case 30:
2135           v = Variant30;
2136           break;
2137         case 31:
2138           v = Variant31;
2139           break;
2140         case 32:
2141           v = Variant32;
2142           break;
2143         case 33:
2144           v = Variant33;
2145           break;
2146         case 34:
2147           v = Variant34;
2148           break;
2149         case 35:
2150           v = Variant35;
2151           break;
2152         case 36:
2153           v = Variant36;
2154           break;
2155         case 37:
2156           v = VariantShogi;
2157           break;
2158         case 38:
2159           v = VariantXiangqi;
2160           break;
2161         case 39:
2162           v = VariantCourier;
2163           break;
2164         case 40:
2165           v = VariantGothic;
2166           break;
2167         case 41:
2168           v = VariantCapablanca;
2169           break;
2170         case 42:
2171           v = VariantKnightmate;
2172           break;
2173         case 43:
2174           v = VariantFairy;
2175           break;
2176         case 44:
2177           v = VariantCylinder;
2178           break;
2179         case 45:
2180           v = VariantFalcon;
2181           break;
2182         case 46:
2183           v = VariantCapaRandom;
2184           break;
2185         case 47:
2186           v = VariantBerolina;
2187           break;
2188         case 48:
2189           v = VariantJanus;
2190           break;
2191         case 49:
2192           v = VariantSuper;
2193           break;
2194         case 50:
2195           v = VariantGreat;
2196           break;
2197         case -1:
2198           /* Found "wild" or "w" in the string but no number;
2199              must assume it's normal chess. */
2200           v = VariantNormal;
2201           break;
2202         default:
2203           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2204           if( (len >= MSG_SIZ) && appData.debugMode )
2205             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2206
2207           DisplayError(buf, 0);
2208           v = VariantUnknown;
2209           break;
2210         }
2211       }
2212     }
2213     if (appData.debugMode) {
2214       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2215               e, wnum, VariantName(v));
2216     }
2217     return v;
2218 }
2219
2220 static int leftover_start = 0, leftover_len = 0;
2221 char star_match[STAR_MATCH_N][MSG_SIZ];
2222
2223 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2224    advance *index beyond it, and set leftover_start to the new value of
2225    *index; else return FALSE.  If pattern contains the character '*', it
2226    matches any sequence of characters not containing '\r', '\n', or the
2227    character following the '*' (if any), and the matched sequence(s) are
2228    copied into star_match.
2229    */
2230 int
2231 looking_at ( char *buf, int *index, char *pattern)
2232 {
2233     char *bufp = &buf[*index], *patternp = pattern;
2234     int star_count = 0;
2235     char *matchp = star_match[0];
2236
2237     for (;;) {
2238         if (*patternp == NULLCHAR) {
2239             *index = leftover_start = bufp - buf;
2240             *matchp = NULLCHAR;
2241             return TRUE;
2242         }
2243         if (*bufp == NULLCHAR) return FALSE;
2244         if (*patternp == '*') {
2245             if (*bufp == *(patternp + 1)) {
2246                 *matchp = NULLCHAR;
2247                 matchp = star_match[++star_count];
2248                 patternp += 2;
2249                 bufp++;
2250                 continue;
2251             } else if (*bufp == '\n' || *bufp == '\r') {
2252                 patternp++;
2253                 if (*patternp == NULLCHAR)
2254                   continue;
2255                 else
2256                   return FALSE;
2257             } else {
2258                 *matchp++ = *bufp++;
2259                 continue;
2260             }
2261         }
2262         if (*patternp != *bufp) return FALSE;
2263         patternp++;
2264         bufp++;
2265     }
2266 }
2267
2268 void
2269 SendToPlayer (char *data, int length)
2270 {
2271     int error, outCount;
2272     outCount = OutputToProcess(NoProc, data, length, &error);
2273     if (outCount < length) {
2274         DisplayFatalError(_("Error writing to display"), error, 1);
2275     }
2276 }
2277
2278 void
2279 PackHolding (char packed[], char *holding)
2280 {
2281     char *p = holding;
2282     char *q = packed;
2283     int runlength = 0;
2284     int curr = 9999;
2285     do {
2286         if (*p == curr) {
2287             runlength++;
2288         } else {
2289             switch (runlength) {
2290               case 0:
2291                 break;
2292               case 1:
2293                 *q++ = curr;
2294                 break;
2295               case 2:
2296                 *q++ = curr;
2297                 *q++ = curr;
2298                 break;
2299               default:
2300                 sprintf(q, "%d", runlength);
2301                 while (*q) q++;
2302                 *q++ = curr;
2303                 break;
2304             }
2305             runlength = 1;
2306             curr = *p;
2307         }
2308     } while (*p++);
2309     *q = NULLCHAR;
2310 }
2311
2312 /* Telnet protocol requests from the front end */
2313 void
2314 TelnetRequest (unsigned char ddww, unsigned char option)
2315 {
2316     unsigned char msg[3];
2317     int outCount, outError;
2318
2319     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2320
2321     if (appData.debugMode) {
2322         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2323         switch (ddww) {
2324           case TN_DO:
2325             ddwwStr = "DO";
2326             break;
2327           case TN_DONT:
2328             ddwwStr = "DONT";
2329             break;
2330           case TN_WILL:
2331             ddwwStr = "WILL";
2332             break;
2333           case TN_WONT:
2334             ddwwStr = "WONT";
2335             break;
2336           default:
2337             ddwwStr = buf1;
2338             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2339             break;
2340         }
2341         switch (option) {
2342           case TN_ECHO:
2343             optionStr = "ECHO";
2344             break;
2345           default:
2346             optionStr = buf2;
2347             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2348             break;
2349         }
2350         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2351     }
2352     msg[0] = TN_IAC;
2353     msg[1] = ddww;
2354     msg[2] = option;
2355     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2356     if (outCount < 3) {
2357         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2358     }
2359 }
2360
2361 void
2362 DoEcho ()
2363 {
2364     if (!appData.icsActive) return;
2365     TelnetRequest(TN_DO, TN_ECHO);
2366 }
2367
2368 void
2369 DontEcho ()
2370 {
2371     if (!appData.icsActive) return;
2372     TelnetRequest(TN_DONT, TN_ECHO);
2373 }
2374
2375 void
2376 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2377 {
2378     /* put the holdings sent to us by the server on the board holdings area */
2379     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2380     char p;
2381     ChessSquare piece;
2382
2383     if(gameInfo.holdingsWidth < 2)  return;
2384     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2385         return; // prevent overwriting by pre-board holdings
2386
2387     if( (int)lowestPiece >= BlackPawn ) {
2388         holdingsColumn = 0;
2389         countsColumn = 1;
2390         holdingsStartRow = BOARD_HEIGHT-1;
2391         direction = -1;
2392     } else {
2393         holdingsColumn = BOARD_WIDTH-1;
2394         countsColumn = BOARD_WIDTH-2;
2395         holdingsStartRow = 0;
2396         direction = 1;
2397     }
2398
2399     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2400         board[i][holdingsColumn] = EmptySquare;
2401         board[i][countsColumn]   = (ChessSquare) 0;
2402     }
2403     while( (p=*holdings++) != NULLCHAR ) {
2404         piece = CharToPiece( ToUpper(p) );
2405         if(piece == EmptySquare) continue;
2406         /*j = (int) piece - (int) WhitePawn;*/
2407         j = PieceToNumber(piece);
2408         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2409         if(j < 0) continue;               /* should not happen */
2410         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2411         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2412         board[holdingsStartRow+j*direction][countsColumn]++;
2413     }
2414 }
2415
2416
2417 void
2418 VariantSwitch (Board board, VariantClass newVariant)
2419 {
2420    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2421    static Board oldBoard;
2422
2423    startedFromPositionFile = FALSE;
2424    if(gameInfo.variant == newVariant) return;
2425
2426    /* [HGM] This routine is called each time an assignment is made to
2427     * gameInfo.variant during a game, to make sure the board sizes
2428     * are set to match the new variant. If that means adding or deleting
2429     * holdings, we shift the playing board accordingly
2430     * This kludge is needed because in ICS observe mode, we get boards
2431     * of an ongoing game without knowing the variant, and learn about the
2432     * latter only later. This can be because of the move list we requested,
2433     * in which case the game history is refilled from the beginning anyway,
2434     * but also when receiving holdings of a crazyhouse game. In the latter
2435     * case we want to add those holdings to the already received position.
2436     */
2437
2438
2439    if (appData.debugMode) {
2440      fprintf(debugFP, "Switch board from %s to %s\n",
2441              VariantName(gameInfo.variant), VariantName(newVariant));
2442      setbuf(debugFP, NULL);
2443    }
2444    shuffleOpenings = 0;       /* [HGM] shuffle */
2445    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2446    switch(newVariant)
2447      {
2448      case VariantShogi:
2449        newWidth = 9;  newHeight = 9;
2450        gameInfo.holdingsSize = 7;
2451      case VariantBughouse:
2452      case VariantCrazyhouse:
2453        newHoldingsWidth = 2; break;
2454      case VariantGreat:
2455        newWidth = 10;
2456      case VariantSuper:
2457        newHoldingsWidth = 2;
2458        gameInfo.holdingsSize = 8;
2459        break;
2460      case VariantGothic:
2461      case VariantCapablanca:
2462      case VariantCapaRandom:
2463        newWidth = 10;
2464      default:
2465        newHoldingsWidth = gameInfo.holdingsSize = 0;
2466      };
2467
2468    if(newWidth  != gameInfo.boardWidth  ||
2469       newHeight != gameInfo.boardHeight ||
2470       newHoldingsWidth != gameInfo.holdingsWidth ) {
2471
2472      /* shift position to new playing area, if needed */
2473      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2474        for(i=0; i<BOARD_HEIGHT; i++)
2475          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2476            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2477              board[i][j];
2478        for(i=0; i<newHeight; i++) {
2479          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2480          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2481        }
2482      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2483        for(i=0; i<BOARD_HEIGHT; i++)
2484          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2485            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2486              board[i][j];
2487      }
2488      board[HOLDINGS_SET] = 0;
2489      gameInfo.boardWidth  = newWidth;
2490      gameInfo.boardHeight = newHeight;
2491      gameInfo.holdingsWidth = newHoldingsWidth;
2492      gameInfo.variant = newVariant;
2493      InitDrawingSizes(-2, 0);
2494    } else gameInfo.variant = newVariant;
2495    CopyBoard(oldBoard, board);   // remember correctly formatted board
2496      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2497    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2498 }
2499
2500 static int loggedOn = FALSE;
2501
2502 /*-- Game start info cache: --*/
2503 int gs_gamenum;
2504 char gs_kind[MSG_SIZ];
2505 static char player1Name[128] = "";
2506 static char player2Name[128] = "";
2507 static char cont_seq[] = "\n\\   ";
2508 static int player1Rating = -1;
2509 static int player2Rating = -1;
2510 /*----------------------------*/
2511
2512 ColorClass curColor = ColorNormal;
2513 int suppressKibitz = 0;
2514
2515 // [HGM] seekgraph
2516 Boolean soughtPending = FALSE;
2517 Boolean seekGraphUp;
2518 #define MAX_SEEK_ADS 200
2519 #define SQUARE 0x80
2520 char *seekAdList[MAX_SEEK_ADS];
2521 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2522 float tcList[MAX_SEEK_ADS];
2523 char colorList[MAX_SEEK_ADS];
2524 int nrOfSeekAds = 0;
2525 int minRating = 1010, maxRating = 2800;
2526 int hMargin = 10, vMargin = 20, h, w;
2527 extern int squareSize, lineGap;
2528
2529 void
2530 PlotSeekAd (int i)
2531 {
2532         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2533         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2534         if(r < minRating+100 && r >=0 ) r = minRating+100;
2535         if(r > maxRating) r = maxRating;
2536         if(tc < 1.f) tc = 1.f;
2537         if(tc > 95.f) tc = 95.f;
2538         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2539         y = ((double)r - minRating)/(maxRating - minRating)
2540             * (h-vMargin-squareSize/8-1) + vMargin;
2541         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2542         if(strstr(seekAdList[i], " u ")) color = 1;
2543         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2544            !strstr(seekAdList[i], "bullet") &&
2545            !strstr(seekAdList[i], "blitz") &&
2546            !strstr(seekAdList[i], "standard") ) color = 2;
2547         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2548         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2549 }
2550
2551 void
2552 PlotSingleSeekAd (int i)
2553 {
2554         PlotSeekAd(i);
2555 }
2556
2557 void
2558 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2559 {
2560         char buf[MSG_SIZ], *ext = "";
2561         VariantClass v = StringToVariant(type);
2562         if(strstr(type, "wild")) {
2563             ext = type + 4; // append wild number
2564             if(v == VariantFischeRandom) type = "chess960"; else
2565             if(v == VariantLoadable) type = "setup"; else
2566             type = VariantName(v);
2567         }
2568         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2569         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2570             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2571             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2572             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2573             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2574             seekNrList[nrOfSeekAds] = nr;
2575             zList[nrOfSeekAds] = 0;
2576             seekAdList[nrOfSeekAds++] = StrSave(buf);
2577             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2578         }
2579 }
2580
2581 void
2582 EraseSeekDot (int i)
2583 {
2584     int x = xList[i], y = yList[i], d=squareSize/4, k;
2585     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2586     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2587     // now replot every dot that overlapped
2588     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2589         int xx = xList[k], yy = yList[k];
2590         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2591             DrawSeekDot(xx, yy, colorList[k]);
2592     }
2593 }
2594
2595 void
2596 RemoveSeekAd (int nr)
2597 {
2598         int i;
2599         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2600             EraseSeekDot(i);
2601             if(seekAdList[i]) free(seekAdList[i]);
2602             seekAdList[i] = seekAdList[--nrOfSeekAds];
2603             seekNrList[i] = seekNrList[nrOfSeekAds];
2604             ratingList[i] = ratingList[nrOfSeekAds];
2605             colorList[i]  = colorList[nrOfSeekAds];
2606             tcList[i] = tcList[nrOfSeekAds];
2607             xList[i]  = xList[nrOfSeekAds];
2608             yList[i]  = yList[nrOfSeekAds];
2609             zList[i]  = zList[nrOfSeekAds];
2610             seekAdList[nrOfSeekAds] = NULL;
2611             break;
2612         }
2613 }
2614
2615 Boolean
2616 MatchSoughtLine (char *line)
2617 {
2618     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2619     int nr, base, inc, u=0; char dummy;
2620
2621     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2622        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2623        (u=1) &&
2624        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2625         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2626         // match: compact and save the line
2627         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2628         return TRUE;
2629     }
2630     return FALSE;
2631 }
2632
2633 int
2634 DrawSeekGraph ()
2635 {
2636     int i;
2637     if(!seekGraphUp) return FALSE;
2638     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2639     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2640
2641     DrawSeekBackground(0, 0, w, h);
2642     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2643     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2644     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2645         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2646         yy = h-1-yy;
2647         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2648         if(i%500 == 0) {
2649             char buf[MSG_SIZ];
2650             snprintf(buf, MSG_SIZ, "%d", i);
2651             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2652         }
2653     }
2654     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2655     for(i=1; i<100; i+=(i<10?1:5)) {
2656         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2657         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2658         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2659             char buf[MSG_SIZ];
2660             snprintf(buf, MSG_SIZ, "%d", i);
2661             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2662         }
2663     }
2664     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2665     return TRUE;
2666 }
2667
2668 int
2669 SeekGraphClick (ClickType click, int x, int y, int moving)
2670 {
2671     static int lastDown = 0, displayed = 0, lastSecond;
2672     if(y < 0) return FALSE;
2673     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2674         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2675         if(!seekGraphUp) return FALSE;
2676         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2677         DrawPosition(TRUE, NULL);
2678         return TRUE;
2679     }
2680     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2681         if(click == Release || moving) return FALSE;
2682         nrOfSeekAds = 0;
2683         soughtPending = TRUE;
2684         SendToICS(ics_prefix);
2685         SendToICS("sought\n"); // should this be "sought all"?
2686     } else { // issue challenge based on clicked ad
2687         int dist = 10000; int i, closest = 0, second = 0;
2688         for(i=0; i<nrOfSeekAds; i++) {
2689             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2690             if(d < dist) { dist = d; closest = i; }
2691             second += (d - zList[i] < 120); // count in-range ads
2692             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2693         }
2694         if(dist < 120) {
2695             char buf[MSG_SIZ];
2696             second = (second > 1);
2697             if(displayed != closest || second != lastSecond) {
2698                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2699                 lastSecond = second; displayed = closest;
2700             }
2701             if(click == Press) {
2702                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2703                 lastDown = closest;
2704                 return TRUE;
2705             } // on press 'hit', only show info
2706             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2707             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2708             SendToICS(ics_prefix);
2709             SendToICS(buf);
2710             return TRUE; // let incoming board of started game pop down the graph
2711         } else if(click == Release) { // release 'miss' is ignored
2712             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2713             if(moving == 2) { // right up-click
2714                 nrOfSeekAds = 0; // refresh graph
2715                 soughtPending = TRUE;
2716                 SendToICS(ics_prefix);
2717                 SendToICS("sought\n"); // should this be "sought all"?
2718             }
2719             return TRUE;
2720         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2721         // press miss or release hit 'pop down' seek graph
2722         seekGraphUp = FALSE;
2723         DrawPosition(TRUE, NULL);
2724     }
2725     return TRUE;
2726 }
2727
2728 void
2729 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2730 {
2731 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2732 #define STARTED_NONE 0
2733 #define STARTED_MOVES 1
2734 #define STARTED_BOARD 2
2735 #define STARTED_OBSERVE 3
2736 #define STARTED_HOLDINGS 4
2737 #define STARTED_CHATTER 5
2738 #define STARTED_COMMENT 6
2739 #define STARTED_MOVES_NOHIDE 7
2740
2741     static int started = STARTED_NONE;
2742     static char parse[20000];
2743     static int parse_pos = 0;
2744     static char buf[BUF_SIZE + 1];
2745     static int firstTime = TRUE, intfSet = FALSE;
2746     static ColorClass prevColor = ColorNormal;
2747     static int savingComment = FALSE;
2748     static int cmatch = 0; // continuation sequence match
2749     char *bp;
2750     char str[MSG_SIZ];
2751     int i, oldi;
2752     int buf_len;
2753     int next_out;
2754     int tkind;
2755     int backup;    /* [DM] For zippy color lines */
2756     char *p;
2757     char talker[MSG_SIZ]; // [HGM] chat
2758     int channel;
2759
2760     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2761
2762     if (appData.debugMode) {
2763       if (!error) {
2764         fprintf(debugFP, "<ICS: ");
2765         show_bytes(debugFP, data, count);
2766         fprintf(debugFP, "\n");
2767       }
2768     }
2769
2770     if (appData.debugMode) { int f = forwardMostMove;
2771         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2772                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2773                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2774     }
2775     if (count > 0) {
2776         /* If last read ended with a partial line that we couldn't parse,
2777            prepend it to the new read and try again. */
2778         if (leftover_len > 0) {
2779             for (i=0; i<leftover_len; i++)
2780               buf[i] = buf[leftover_start + i];
2781         }
2782
2783     /* copy new characters into the buffer */
2784     bp = buf + leftover_len;
2785     buf_len=leftover_len;
2786     for (i=0; i<count; i++)
2787     {
2788         // ignore these
2789         if (data[i] == '\r')
2790             continue;
2791
2792         // join lines split by ICS?
2793         if (!appData.noJoin)
2794         {
2795             /*
2796                 Joining just consists of finding matches against the
2797                 continuation sequence, and discarding that sequence
2798                 if found instead of copying it.  So, until a match
2799                 fails, there's nothing to do since it might be the
2800                 complete sequence, and thus, something we don't want
2801                 copied.
2802             */
2803             if (data[i] == cont_seq[cmatch])
2804             {
2805                 cmatch++;
2806                 if (cmatch == strlen(cont_seq))
2807                 {
2808                     cmatch = 0; // complete match.  just reset the counter
2809
2810                     /*
2811                         it's possible for the ICS to not include the space
2812                         at the end of the last word, making our [correct]
2813                         join operation fuse two separate words.  the server
2814                         does this when the space occurs at the width setting.
2815                     */
2816                     if (!buf_len || buf[buf_len-1] != ' ')
2817                     {
2818                         *bp++ = ' ';
2819                         buf_len++;
2820                     }
2821                 }
2822                 continue;
2823             }
2824             else if (cmatch)
2825             {
2826                 /*
2827                     match failed, so we have to copy what matched before
2828                     falling through and copying this character.  In reality,
2829                     this will only ever be just the newline character, but
2830                     it doesn't hurt to be precise.
2831                 */
2832                 strncpy(bp, cont_seq, cmatch);
2833                 bp += cmatch;
2834                 buf_len += cmatch;
2835                 cmatch = 0;
2836             }
2837         }
2838
2839         // copy this char
2840         *bp++ = data[i];
2841         buf_len++;
2842     }
2843
2844         buf[buf_len] = NULLCHAR;
2845 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2846         next_out = 0;
2847         leftover_start = 0;
2848
2849         i = 0;
2850         while (i < buf_len) {
2851             /* Deal with part of the TELNET option negotiation
2852                protocol.  We refuse to do anything beyond the
2853                defaults, except that we allow the WILL ECHO option,
2854                which ICS uses to turn off password echoing when we are
2855                directly connected to it.  We reject this option
2856                if localLineEditing mode is on (always on in xboard)
2857                and we are talking to port 23, which might be a real
2858                telnet server that will try to keep WILL ECHO on permanently.
2859              */
2860             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2861                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2862                 unsigned char option;
2863                 oldi = i;
2864                 switch ((unsigned char) buf[++i]) {
2865                   case TN_WILL:
2866                     if (appData.debugMode)
2867                       fprintf(debugFP, "\n<WILL ");
2868                     switch (option = (unsigned char) buf[++i]) {
2869                       case TN_ECHO:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "ECHO ");
2872                         /* Reply only if this is a change, according
2873                            to the protocol rules. */
2874                         if (remoteEchoOption) break;
2875                         if (appData.localLineEditing &&
2876                             atoi(appData.icsPort) == TN_PORT) {
2877                             TelnetRequest(TN_DONT, TN_ECHO);
2878                         } else {
2879                             EchoOff();
2880                             TelnetRequest(TN_DO, TN_ECHO);
2881                             remoteEchoOption = TRUE;
2882                         }
2883                         break;
2884                       default:
2885                         if (appData.debugMode)
2886                           fprintf(debugFP, "%d ", option);
2887                         /* Whatever this is, we don't want it. */
2888                         TelnetRequest(TN_DONT, option);
2889                         break;
2890                     }
2891                     break;
2892                   case TN_WONT:
2893                     if (appData.debugMode)
2894                       fprintf(debugFP, "\n<WONT ");
2895                     switch (option = (unsigned char) buf[++i]) {
2896                       case TN_ECHO:
2897                         if (appData.debugMode)
2898                           fprintf(debugFP, "ECHO ");
2899                         /* Reply only if this is a change, according
2900                            to the protocol rules. */
2901                         if (!remoteEchoOption) break;
2902                         EchoOn();
2903                         TelnetRequest(TN_DONT, TN_ECHO);
2904                         remoteEchoOption = FALSE;
2905                         break;
2906                       default:
2907                         if (appData.debugMode)
2908                           fprintf(debugFP, "%d ", (unsigned char) option);
2909                         /* Whatever this is, it must already be turned
2910                            off, because we never agree to turn on
2911                            anything non-default, so according to the
2912                            protocol rules, we don't reply. */
2913                         break;
2914                     }
2915                     break;
2916                   case TN_DO:
2917                     if (appData.debugMode)
2918                       fprintf(debugFP, "\n<DO ");
2919                     switch (option = (unsigned char) buf[++i]) {
2920                       default:
2921                         /* Whatever this is, we refuse to do it. */
2922                         if (appData.debugMode)
2923                           fprintf(debugFP, "%d ", option);
2924                         TelnetRequest(TN_WONT, option);
2925                         break;
2926                     }
2927                     break;
2928                   case TN_DONT:
2929                     if (appData.debugMode)
2930                       fprintf(debugFP, "\n<DONT ");
2931                     switch (option = (unsigned char) buf[++i]) {
2932                       default:
2933                         if (appData.debugMode)
2934                           fprintf(debugFP, "%d ", option);
2935                         /* Whatever this is, we are already not doing
2936                            it, because we never agree to do anything
2937                            non-default, so according to the protocol
2938                            rules, we don't reply. */
2939                         break;
2940                     }
2941                     break;
2942                   case TN_IAC:
2943                     if (appData.debugMode)
2944                       fprintf(debugFP, "\n<IAC ");
2945                     /* Doubled IAC; pass it through */
2946                     i--;
2947                     break;
2948                   default:
2949                     if (appData.debugMode)
2950                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2951                     /* Drop all other telnet commands on the floor */
2952                     break;
2953                 }
2954                 if (oldi > next_out)
2955                   SendToPlayer(&buf[next_out], oldi - next_out);
2956                 if (++i > next_out)
2957                   next_out = i;
2958                 continue;
2959             }
2960
2961             /* OK, this at least will *usually* work */
2962             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2963                 loggedOn = TRUE;
2964             }
2965
2966             if (loggedOn && !intfSet) {
2967                 if (ics_type == ICS_ICC) {
2968                   snprintf(str, MSG_SIZ,
2969                           "/set-quietly interface %s\n/set-quietly style 12\n",
2970                           programVersion);
2971                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2972                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2973                 } else if (ics_type == ICS_CHESSNET) {
2974                   snprintf(str, MSG_SIZ, "/style 12\n");
2975                 } else {
2976                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2977                   strcat(str, programVersion);
2978                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2979                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2980                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2981 #ifdef WIN32
2982                   strcat(str, "$iset nohighlight 1\n");
2983 #endif
2984                   strcat(str, "$iset lock 1\n$style 12\n");
2985                 }
2986                 SendToICS(str);
2987                 NotifyFrontendLogin();
2988                 intfSet = TRUE;
2989             }
2990
2991             if (started == STARTED_COMMENT) {
2992                 /* Accumulate characters in comment */
2993                 parse[parse_pos++] = buf[i];
2994                 if (buf[i] == '\n') {
2995                     parse[parse_pos] = NULLCHAR;
2996                     if(chattingPartner>=0) {
2997                         char mess[MSG_SIZ];
2998                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2999                         OutputChatMessage(chattingPartner, mess);
3000                         chattingPartner = -1;
3001                         next_out = i+1; // [HGM] suppress printing in ICS window
3002                     } else
3003                     if(!suppressKibitz) // [HGM] kibitz
3004                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3005                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3006                         int nrDigit = 0, nrAlph = 0, j;
3007                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3008                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3009                         parse[parse_pos] = NULLCHAR;
3010                         // try to be smart: if it does not look like search info, it should go to
3011                         // ICS interaction window after all, not to engine-output window.
3012                         for(j=0; j<parse_pos; j++) { // count letters and digits
3013                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3014                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3015                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3016                         }
3017                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3018                             int depth=0; float score;
3019                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3020                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3021                                 pvInfoList[forwardMostMove-1].depth = depth;
3022                                 pvInfoList[forwardMostMove-1].score = 100*score;
3023                             }
3024                             OutputKibitz(suppressKibitz, parse);
3025                         } else {
3026                             char tmp[MSG_SIZ];
3027                             if(gameMode == IcsObserving) // restore original ICS messages
3028                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3029                             else
3030                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3031                             SendToPlayer(tmp, strlen(tmp));
3032                         }
3033                         next_out = i+1; // [HGM] suppress printing in ICS window
3034                     }
3035                     started = STARTED_NONE;
3036                 } else {
3037                     /* Don't match patterns against characters in comment */
3038                     i++;
3039                     continue;
3040                 }
3041             }
3042             if (started == STARTED_CHATTER) {
3043                 if (buf[i] != '\n') {
3044                     /* Don't match patterns against characters in chatter */
3045                     i++;
3046                     continue;
3047                 }
3048                 started = STARTED_NONE;
3049                 if(suppressKibitz) next_out = i+1;
3050             }
3051
3052             /* Kludge to deal with rcmd protocol */
3053             if (firstTime && looking_at(buf, &i, "\001*")) {
3054                 DisplayFatalError(&buf[1], 0, 1);
3055                 continue;
3056             } else {
3057                 firstTime = FALSE;
3058             }
3059
3060             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3061                 ics_type = ICS_ICC;
3062                 ics_prefix = "/";
3063                 if (appData.debugMode)
3064                   fprintf(debugFP, "ics_type %d\n", ics_type);
3065                 continue;
3066             }
3067             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3068                 ics_type = ICS_FICS;
3069                 ics_prefix = "$";
3070                 if (appData.debugMode)
3071                   fprintf(debugFP, "ics_type %d\n", ics_type);
3072                 continue;
3073             }
3074             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3075                 ics_type = ICS_CHESSNET;
3076                 ics_prefix = "/";
3077                 if (appData.debugMode)
3078                   fprintf(debugFP, "ics_type %d\n", ics_type);
3079                 continue;
3080             }
3081
3082             if (!loggedOn &&
3083                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3084                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3085                  looking_at(buf, &i, "will be \"*\""))) {
3086               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3087               continue;
3088             }
3089
3090             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3091               char buf[MSG_SIZ];
3092               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3093               DisplayIcsInteractionTitle(buf);
3094               have_set_title = TRUE;
3095             }
3096
3097             /* skip finger notes */
3098             if (started == STARTED_NONE &&
3099                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3100                  (buf[i] == '1' && buf[i+1] == '0')) &&
3101                 buf[i+2] == ':' && buf[i+3] == ' ') {
3102               started = STARTED_CHATTER;
3103               i += 3;
3104               continue;
3105             }
3106
3107             oldi = i;
3108             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3109             if(appData.seekGraph) {
3110                 if(soughtPending && MatchSoughtLine(buf+i)) {
3111                     i = strstr(buf+i, "rated") - buf;
3112                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3113                     next_out = leftover_start = i;
3114                     started = STARTED_CHATTER;
3115                     suppressKibitz = TRUE;
3116                     continue;
3117                 }
3118                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3119                         && looking_at(buf, &i, "* ads displayed")) {
3120                     soughtPending = FALSE;
3121                     seekGraphUp = TRUE;
3122                     DrawSeekGraph();
3123                     continue;
3124                 }
3125                 if(appData.autoRefresh) {
3126                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3127                         int s = (ics_type == ICS_ICC); // ICC format differs
3128                         if(seekGraphUp)
3129                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3130                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3131                         looking_at(buf, &i, "*% "); // eat prompt
3132                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3133                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134                         next_out = i; // suppress
3135                         continue;
3136                     }
3137                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3138                         char *p = star_match[0];
3139                         while(*p) {
3140                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3141                             while(*p && *p++ != ' '); // next
3142                         }
3143                         looking_at(buf, &i, "*% "); // eat prompt
3144                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = i;
3146                         continue;
3147                     }
3148                 }
3149             }
3150
3151             /* skip formula vars */
3152             if (started == STARTED_NONE &&
3153                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3154               started = STARTED_CHATTER;
3155               i += 3;
3156               continue;
3157             }
3158
3159             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3160             if (appData.autoKibitz && started == STARTED_NONE &&
3161                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3162                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3163                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3164                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3165                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3166                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3167                         suppressKibitz = TRUE;
3168                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3169                         next_out = i;
3170                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3171                                 && (gameMode == IcsPlayingWhite)) ||
3172                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3173                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3174                             started = STARTED_CHATTER; // own kibitz we simply discard
3175                         else {
3176                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3177                             parse_pos = 0; parse[0] = NULLCHAR;
3178                             savingComment = TRUE;
3179                             suppressKibitz = gameMode != IcsObserving ? 2 :
3180                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3181                         }
3182                         continue;
3183                 } else
3184                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3185                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3186                          && atoi(star_match[0])) {
3187                     // suppress the acknowledgements of our own autoKibitz
3188                     char *p;
3189                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3191                     SendToPlayer(star_match[0], strlen(star_match[0]));
3192                     if(looking_at(buf, &i, "*% ")) // eat prompt
3193                         suppressKibitz = FALSE;
3194                     next_out = i;
3195                     continue;
3196                 }
3197             } // [HGM] kibitz: end of patch
3198
3199             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3200
3201             // [HGM] chat: intercept tells by users for which we have an open chat window
3202             channel = -1;
3203             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3204                                            looking_at(buf, &i, "* whispers:") ||
3205                                            looking_at(buf, &i, "* kibitzes:") ||
3206                                            looking_at(buf, &i, "* shouts:") ||
3207                                            looking_at(buf, &i, "* c-shouts:") ||
3208                                            looking_at(buf, &i, "--> * ") ||
3209                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3211                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3212                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3213                 int p;
3214                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3215                 chattingPartner = -1;
3216
3217                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3218                 for(p=0; p<MAX_CHAT; p++) {
3219                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3220                     talker[0] = '['; strcat(talker, "] ");
3221                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3222                     chattingPartner = p; break;
3223                     }
3224                 } else
3225                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3226                 for(p=0; p<MAX_CHAT; p++) {
3227                     if(!strcmp("kibitzes", chatPartner[p])) {
3228                         talker[0] = '['; strcat(talker, "] ");
3229                         chattingPartner = p; break;
3230                     }
3231                 } else
3232                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3233                 for(p=0; p<MAX_CHAT; p++) {
3234                     if(!strcmp("whispers", chatPartner[p])) {
3235                         talker[0] = '['; strcat(talker, "] ");
3236                         chattingPartner = p; break;
3237                     }
3238                 } else
3239                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3240                   if(buf[i-8] == '-' && buf[i-3] == 't')
3241                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3242                     if(!strcmp("c-shouts", chatPartner[p])) {
3243                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3244                         chattingPartner = p; break;
3245                     }
3246                   }
3247                   if(chattingPartner < 0)
3248                   for(p=0; p<MAX_CHAT; p++) {
3249                     if(!strcmp("shouts", chatPartner[p])) {
3250                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3251                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3252                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3253                         chattingPartner = p; break;
3254                     }
3255                   }
3256                 }
3257                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3258                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3259                     talker[0] = 0; Colorize(ColorTell, FALSE);
3260                     chattingPartner = p; break;
3261                 }
3262                 if(chattingPartner<0) i = oldi; else {
3263                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3264                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3265                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266                     started = STARTED_COMMENT;
3267                     parse_pos = 0; parse[0] = NULLCHAR;
3268                     savingComment = 3 + chattingPartner; // counts as TRUE
3269                     suppressKibitz = TRUE;
3270                     continue;
3271                 }
3272             } // [HGM] chat: end of patch
3273
3274           backup = i;
3275             if (appData.zippyTalk || appData.zippyPlay) {
3276                 /* [DM] Backup address for color zippy lines */
3277 #if ZIPPY
3278                if (loggedOn == TRUE)
3279                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3280                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3281 #endif
3282             } // [DM] 'else { ' deleted
3283                 if (
3284                     /* Regular tells and says */
3285                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3286                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3287                     looking_at(buf, &i, "* says: ") ||
3288                     /* Don't color "message" or "messages" output */
3289                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3290                     looking_at(buf, &i, "*. * at *:*: ") ||
3291                     looking_at(buf, &i, "--* (*:*): ") ||
3292                     /* Message notifications (same color as tells) */
3293                     looking_at(buf, &i, "* has left a message ") ||
3294                     looking_at(buf, &i, "* just sent you a message:\n") ||
3295                     /* Whispers and kibitzes */
3296                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3297                     looking_at(buf, &i, "* kibitzes: ") ||
3298                     /* Channel tells */
3299                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3300
3301                   if (tkind == 1 && strchr(star_match[0], ':')) {
3302                       /* Avoid "tells you:" spoofs in channels */
3303                      tkind = 3;
3304                   }
3305                   if (star_match[0][0] == NULLCHAR ||
3306                       strchr(star_match[0], ' ') ||
3307                       (tkind == 3 && strchr(star_match[1], ' '))) {
3308                     /* Reject bogus matches */
3309                     i = oldi;
3310                   } else {
3311                     if (appData.colorize) {
3312                       if (oldi > next_out) {
3313                         SendToPlayer(&buf[next_out], oldi - next_out);
3314                         next_out = oldi;
3315                       }
3316                       switch (tkind) {
3317                       case 1:
3318                         Colorize(ColorTell, FALSE);
3319                         curColor = ColorTell;
3320                         break;
3321                       case 2:
3322                         Colorize(ColorKibitz, FALSE);
3323                         curColor = ColorKibitz;
3324                         break;
3325                       case 3:
3326                         p = strrchr(star_match[1], '(');
3327                         if (p == NULL) {
3328                           p = star_match[1];
3329                         } else {
3330                           p++;
3331                         }
3332                         if (atoi(p) == 1) {
3333                           Colorize(ColorChannel1, FALSE);
3334                           curColor = ColorChannel1;
3335                         } else {
3336                           Colorize(ColorChannel, FALSE);
3337                           curColor = ColorChannel;
3338                         }
3339                         break;
3340                       case 5:
3341                         curColor = ColorNormal;
3342                         break;
3343                       }
3344                     }
3345                     if (started == STARTED_NONE && appData.autoComment &&
3346                         (gameMode == IcsObserving ||
3347                          gameMode == IcsPlayingWhite ||
3348                          gameMode == IcsPlayingBlack)) {
3349                       parse_pos = i - oldi;
3350                       memcpy(parse, &buf[oldi], parse_pos);
3351                       parse[parse_pos] = NULLCHAR;
3352                       started = STARTED_COMMENT;
3353                       savingComment = TRUE;
3354                     } else {
3355                       started = STARTED_CHATTER;
3356                       savingComment = FALSE;
3357                     }
3358                     loggedOn = TRUE;
3359                     continue;
3360                   }
3361                 }
3362
3363                 if (looking_at(buf, &i, "* s-shouts: ") ||
3364                     looking_at(buf, &i, "* c-shouts: ")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorSShout, FALSE);
3371                         curColor = ColorSShout;
3372                     }
3373                     loggedOn = TRUE;
3374                     started = STARTED_CHATTER;
3375                     continue;
3376                 }
3377
3378                 if (looking_at(buf, &i, "--->")) {
3379                     loggedOn = TRUE;
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* shouts: ") ||
3384                     looking_at(buf, &i, "--> ")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorShout, FALSE);
3391                         curColor = ColorShout;
3392                     }
3393                     loggedOn = TRUE;
3394                     started = STARTED_CHATTER;
3395                     continue;
3396                 }
3397
3398                 if (looking_at( buf, &i, "Challenge:")) {
3399                     if (appData.colorize) {
3400                         if (oldi > next_out) {
3401                             SendToPlayer(&buf[next_out], oldi - next_out);
3402                             next_out = oldi;
3403                         }
3404                         Colorize(ColorChallenge, FALSE);
3405                         curColor = ColorChallenge;
3406                     }
3407                     loggedOn = TRUE;
3408                     continue;
3409                 }
3410
3411                 if (looking_at(buf, &i, "* offers you") ||
3412                     looking_at(buf, &i, "* offers to be") ||
3413                     looking_at(buf, &i, "* would like to") ||
3414                     looking_at(buf, &i, "* requests to") ||
3415                     looking_at(buf, &i, "Your opponent offers") ||
3416                     looking_at(buf, &i, "Your opponent requests")) {
3417
3418                     if (appData.colorize) {
3419                         if (oldi > next_out) {
3420                             SendToPlayer(&buf[next_out], oldi - next_out);
3421                             next_out = oldi;
3422                         }
3423                         Colorize(ColorRequest, FALSE);
3424                         curColor = ColorRequest;
3425                     }
3426                     continue;
3427                 }
3428
3429                 if (looking_at(buf, &i, "* (*) seeking")) {
3430                     if (appData.colorize) {
3431                         if (oldi > next_out) {
3432                             SendToPlayer(&buf[next_out], oldi - next_out);
3433                             next_out = oldi;
3434                         }
3435                         Colorize(ColorSeek, FALSE);
3436                         curColor = ColorSeek;
3437                     }
3438                     continue;
3439             }
3440
3441           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3442
3443             if (looking_at(buf, &i, "\\   ")) {
3444                 if (prevColor != ColorNormal) {
3445                     if (oldi > next_out) {
3446                         SendToPlayer(&buf[next_out], oldi - next_out);
3447                         next_out = oldi;
3448                     }
3449                     Colorize(prevColor, TRUE);
3450                     curColor = prevColor;
3451                 }
3452                 if (savingComment) {
3453                     parse_pos = i - oldi;
3454                     memcpy(parse, &buf[oldi], parse_pos);
3455                     parse[parse_pos] = NULLCHAR;
3456                     started = STARTED_COMMENT;
3457                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3458                         chattingPartner = savingComment - 3; // kludge to remember the box
3459                 } else {
3460                     started = STARTED_CHATTER;
3461                 }
3462                 continue;
3463             }
3464
3465             if (looking_at(buf, &i, "Black Strength :") ||
3466                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3467                 looking_at(buf, &i, "<10>") ||
3468                 looking_at(buf, &i, "#@#")) {
3469                 /* Wrong board style */
3470                 loggedOn = TRUE;
3471                 SendToICS(ics_prefix);
3472                 SendToICS("set style 12\n");
3473                 SendToICS(ics_prefix);
3474                 SendToICS("refresh\n");
3475                 continue;
3476             }
3477
3478             if (looking_at(buf, &i, "login:")) {
3479               if (!have_sent_ICS_logon) {
3480                 if(ICSInitScript())
3481                   have_sent_ICS_logon = 1;
3482                 else // no init script was found
3483                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3484               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3485                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3486               }
3487                 continue;
3488             }
3489
3490             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3491                 (looking_at(buf, &i, "\n<12> ") ||
3492                  looking_at(buf, &i, "<12> "))) {
3493                 loggedOn = TRUE;
3494                 if (oldi > next_out) {
3495                     SendToPlayer(&buf[next_out], oldi - next_out);
3496                 }
3497                 next_out = i;
3498                 started = STARTED_BOARD;
3499                 parse_pos = 0;
3500                 continue;
3501             }
3502
3503             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3504                 looking_at(buf, &i, "<b1> ")) {
3505                 if (oldi > next_out) {
3506                     SendToPlayer(&buf[next_out], oldi - next_out);
3507                 }
3508                 next_out = i;
3509                 started = STARTED_HOLDINGS;
3510                 parse_pos = 0;
3511                 continue;
3512             }
3513
3514             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3515                 loggedOn = TRUE;
3516                 /* Header for a move list -- first line */
3517
3518                 switch (ics_getting_history) {
3519                   case H_FALSE:
3520                     switch (gameMode) {
3521                       case IcsIdle:
3522                       case BeginningOfGame:
3523                         /* User typed "moves" or "oldmoves" while we
3524                            were idle.  Pretend we asked for these
3525                            moves and soak them up so user can step
3526                            through them and/or save them.
3527                            */
3528                         Reset(FALSE, TRUE);
3529                         gameMode = IcsObserving;
3530                         ModeHighlight();
3531                         ics_gamenum = -1;
3532                         ics_getting_history = H_GOT_UNREQ_HEADER;
3533                         break;
3534                       case EditGame: /*?*/
3535                       case EditPosition: /*?*/
3536                         /* Should above feature work in these modes too? */
3537                         /* For now it doesn't */
3538                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3539                         break;
3540                       default:
3541                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3542                         break;
3543                     }
3544                     break;
3545                   case H_REQUESTED:
3546                     /* Is this the right one? */
3547                     if (gameInfo.white && gameInfo.black &&
3548                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3549                         strcmp(gameInfo.black, star_match[2]) == 0) {
3550                         /* All is well */
3551                         ics_getting_history = H_GOT_REQ_HEADER;
3552                     }
3553                     break;
3554                   case H_GOT_REQ_HEADER:
3555                   case H_GOT_UNREQ_HEADER:
3556                   case H_GOT_UNWANTED_HEADER:
3557                   case H_GETTING_MOVES:
3558                     /* Should not happen */
3559                     DisplayError(_("Error gathering move list: two headers"), 0);
3560                     ics_getting_history = H_FALSE;
3561                     break;
3562                 }
3563
3564                 /* Save player ratings into gameInfo if needed */
3565                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3566                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3567                     (gameInfo.whiteRating == -1 ||
3568                      gameInfo.blackRating == -1)) {
3569
3570                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3571                     gameInfo.blackRating = string_to_rating(star_match[3]);
3572                     if (appData.debugMode)
3573                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3574                               gameInfo.whiteRating, gameInfo.blackRating);
3575                 }
3576                 continue;
3577             }
3578
3579             if (looking_at(buf, &i,
3580               "* * match, initial time: * minute*, increment: * second")) {
3581                 /* Header for a move list -- second line */
3582                 /* Initial board will follow if this is a wild game */
3583                 if (gameInfo.event != NULL) free(gameInfo.event);
3584                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3585                 gameInfo.event = StrSave(str);
3586                 /* [HGM] we switched variant. Translate boards if needed. */
3587                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3588                 continue;
3589             }
3590
3591             if (looking_at(buf, &i, "Move  ")) {
3592                 /* Beginning of a move list */
3593                 switch (ics_getting_history) {
3594                   case H_FALSE:
3595                     /* Normally should not happen */
3596                     /* Maybe user hit reset while we were parsing */
3597                     break;
3598                   case H_REQUESTED:
3599                     /* Happens if we are ignoring a move list that is not
3600                      * the one we just requested.  Common if the user
3601                      * tries to observe two games without turning off
3602                      * getMoveList */
3603                     break;
3604                   case H_GETTING_MOVES:
3605                     /* Should not happen */
3606                     DisplayError(_("Error gathering move list: nested"), 0);
3607                     ics_getting_history = H_FALSE;
3608                     break;
3609                   case H_GOT_REQ_HEADER:
3610                     ics_getting_history = H_GETTING_MOVES;
3611                     started = STARTED_MOVES;
3612                     parse_pos = 0;
3613                     if (oldi > next_out) {
3614                         SendToPlayer(&buf[next_out], oldi - next_out);
3615                     }
3616                     break;
3617                   case H_GOT_UNREQ_HEADER:
3618                     ics_getting_history = H_GETTING_MOVES;
3619                     started = STARTED_MOVES_NOHIDE;
3620                     parse_pos = 0;
3621                     break;
3622                   case H_GOT_UNWANTED_HEADER:
3623                     ics_getting_history = H_FALSE;
3624                     break;
3625                 }
3626                 continue;
3627             }
3628
3629             if (looking_at(buf, &i, "% ") ||
3630                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3631                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3632                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3633                     soughtPending = FALSE;
3634                     seekGraphUp = TRUE;
3635                     DrawSeekGraph();
3636                 }
3637                 if(suppressKibitz) next_out = i;
3638                 savingComment = FALSE;
3639                 suppressKibitz = 0;
3640                 switch (started) {
3641                   case STARTED_MOVES:
3642                   case STARTED_MOVES_NOHIDE:
3643                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3644                     parse[parse_pos + i - oldi] = NULLCHAR;
3645                     ParseGameHistory(parse);
3646 #if ZIPPY
3647                     if (appData.zippyPlay && first.initDone) {
3648                         FeedMovesToProgram(&first, forwardMostMove);
3649                         if (gameMode == IcsPlayingWhite) {
3650                             if (WhiteOnMove(forwardMostMove)) {
3651                                 if (first.sendTime) {
3652                                   if (first.useColors) {
3653                                     SendToProgram("black\n", &first);
3654                                   }
3655                                   SendTimeRemaining(&first, TRUE);
3656                                 }
3657                                 if (first.useColors) {
3658                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3659                                 }
3660                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3661                                 first.maybeThinking = TRUE;
3662                             } else {
3663                                 if (first.usePlayother) {
3664                                   if (first.sendTime) {
3665                                     SendTimeRemaining(&first, TRUE);
3666                                   }
3667                                   SendToProgram("playother\n", &first);
3668                                   firstMove = FALSE;
3669                                 } else {
3670                                   firstMove = TRUE;
3671                                 }
3672                             }
3673                         } else if (gameMode == IcsPlayingBlack) {
3674                             if (!WhiteOnMove(forwardMostMove)) {
3675                                 if (first.sendTime) {
3676                                   if (first.useColors) {
3677                                     SendToProgram("white\n", &first);
3678                                   }
3679                                   SendTimeRemaining(&first, FALSE);
3680                                 }
3681                                 if (first.useColors) {
3682                                   SendToProgram("black\n", &first);
3683                                 }
3684                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3685                                 first.maybeThinking = TRUE;
3686                             } else {
3687                                 if (first.usePlayother) {
3688                                   if (first.sendTime) {
3689                                     SendTimeRemaining(&first, FALSE);
3690                                   }
3691                                   SendToProgram("playother\n", &first);
3692                                   firstMove = FALSE;
3693                                 } else {
3694                                   firstMove = TRUE;
3695                                 }
3696                             }
3697                         }
3698                     }
3699 #endif
3700                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3701                         /* Moves came from oldmoves or moves command
3702                            while we weren't doing anything else.
3703                            */
3704                         currentMove = forwardMostMove;
3705                         ClearHighlights();/*!!could figure this out*/
3706                         flipView = appData.flipView;
3707                         DrawPosition(TRUE, boards[currentMove]);
3708                         DisplayBothClocks();
3709                         snprintf(str, MSG_SIZ, "%s %s %s",
3710                                 gameInfo.white, _("vs."),  gameInfo.black);
3711                         DisplayTitle(str);
3712                         gameMode = IcsIdle;
3713                     } else {
3714                         /* Moves were history of an active game */
3715                         if (gameInfo.resultDetails != NULL) {
3716                             free(gameInfo.resultDetails);
3717                             gameInfo.resultDetails = NULL;
3718                         }
3719                     }
3720                     HistorySet(parseList, backwardMostMove,
3721                                forwardMostMove, currentMove-1);
3722                     DisplayMove(currentMove - 1);
3723                     if (started == STARTED_MOVES) next_out = i;
3724                     started = STARTED_NONE;
3725                     ics_getting_history = H_FALSE;
3726                     break;
3727
3728                   case STARTED_OBSERVE:
3729                     started = STARTED_NONE;
3730                     SendToICS(ics_prefix);
3731                     SendToICS("refresh\n");
3732                     break;
3733
3734                   default:
3735                     break;
3736                 }
3737                 if(bookHit) { // [HGM] book: simulate book reply
3738                     static char bookMove[MSG_SIZ]; // a bit generous?
3739
3740                     programStats.nodes = programStats.depth = programStats.time =
3741                     programStats.score = programStats.got_only_move = 0;
3742                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3743
3744                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3745                     strcat(bookMove, bookHit);
3746                     HandleMachineMove(bookMove, &first);
3747                 }
3748                 continue;
3749             }
3750
3751             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3752                  started == STARTED_HOLDINGS ||
3753                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3754                 /* Accumulate characters in move list or board */
3755                 parse[parse_pos++] = buf[i];
3756             }
3757
3758             /* Start of game messages.  Mostly we detect start of game
3759                when the first board image arrives.  On some versions
3760                of the ICS, though, we need to do a "refresh" after starting
3761                to observe in order to get the current board right away. */
3762             if (looking_at(buf, &i, "Adding game * to observation list")) {
3763                 started = STARTED_OBSERVE;
3764                 continue;
3765             }
3766
3767             /* Handle auto-observe */
3768             if (appData.autoObserve &&
3769                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3770                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3771                 char *player;
3772                 /* Choose the player that was highlighted, if any. */
3773                 if (star_match[0][0] == '\033' ||
3774                     star_match[1][0] != '\033') {
3775                     player = star_match[0];
3776                 } else {
3777                     player = star_match[2];
3778                 }
3779                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3780                         ics_prefix, StripHighlightAndTitle(player));
3781                 SendToICS(str);
3782
3783                 /* Save ratings from notify string */
3784                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3785                 player1Rating = string_to_rating(star_match[1]);
3786                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3787                 player2Rating = string_to_rating(star_match[3]);
3788
3789                 if (appData.debugMode)
3790                   fprintf(debugFP,
3791                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3792                           player1Name, player1Rating,
3793                           player2Name, player2Rating);
3794
3795                 continue;
3796             }
3797
3798             /* Deal with automatic examine mode after a game,
3799                and with IcsObserving -> IcsExamining transition */
3800             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3801                 looking_at(buf, &i, "has made you an examiner of game *")) {
3802
3803                 int gamenum = atoi(star_match[0]);
3804                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3805                     gamenum == ics_gamenum) {
3806                     /* We were already playing or observing this game;
3807                        no need to refetch history */
3808                     gameMode = IcsExamining;
3809                     if (pausing) {
3810                         pauseExamForwardMostMove = forwardMostMove;
3811                     } else if (currentMove < forwardMostMove) {
3812                         ForwardInner(forwardMostMove);
3813                     }
3814                 } else {
3815                     /* I don't think this case really can happen */
3816                     SendToICS(ics_prefix);
3817                     SendToICS("refresh\n");
3818                 }
3819                 continue;
3820             }
3821
3822             /* Error messages */
3823 //          if (ics_user_moved) {
3824             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3825                 if (looking_at(buf, &i, "Illegal move") ||
3826                     looking_at(buf, &i, "Not a legal move") ||
3827                     looking_at(buf, &i, "Your king is in check") ||
3828                     looking_at(buf, &i, "It isn't your turn") ||
3829                     looking_at(buf, &i, "It is not your move")) {
3830                     /* Illegal move */
3831                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3832                         currentMove = forwardMostMove-1;
3833                         DisplayMove(currentMove - 1); /* before DMError */
3834                         DrawPosition(FALSE, boards[currentMove]);
3835                         SwitchClocks(forwardMostMove-1); // [HGM] race
3836                         DisplayBothClocks();
3837                     }
3838                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3839                     ics_user_moved = 0;
3840                     continue;
3841                 }
3842             }
3843
3844             if (looking_at(buf, &i, "still have time") ||
3845                 looking_at(buf, &i, "not out of time") ||
3846                 looking_at(buf, &i, "either player is out of time") ||
3847                 looking_at(buf, &i, "has timeseal; checking")) {
3848                 /* We must have called his flag a little too soon */
3849                 whiteFlag = blackFlag = FALSE;
3850                 continue;
3851             }
3852
3853             if (looking_at(buf, &i, "added * seconds to") ||
3854                 looking_at(buf, &i, "seconds were added to")) {
3855                 /* Update the clocks */
3856                 SendToICS(ics_prefix);
3857                 SendToICS("refresh\n");
3858                 continue;
3859             }
3860
3861             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3862                 ics_clock_paused = TRUE;
3863                 StopClocks();
3864                 continue;
3865             }
3866
3867             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3868                 ics_clock_paused = FALSE;
3869                 StartClocks();
3870                 continue;
3871             }
3872
3873             /* Grab player ratings from the Creating: message.
3874                Note we have to check for the special case when
3875                the ICS inserts things like [white] or [black]. */
3876             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3877                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3878                 /* star_matches:
3879                    0    player 1 name (not necessarily white)
3880                    1    player 1 rating
3881                    2    empty, white, or black (IGNORED)
3882                    3    player 2 name (not necessarily black)
3883                    4    player 2 rating
3884
3885                    The names/ratings are sorted out when the game
3886                    actually starts (below).
3887                 */
3888                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3889                 player1Rating = string_to_rating(star_match[1]);
3890                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3891                 player2Rating = string_to_rating(star_match[4]);
3892
3893                 if (appData.debugMode)
3894                   fprintf(debugFP,
3895                           "Ratings from 'Creating:' %s %d, %s %d\n",
3896                           player1Name, player1Rating,
3897                           player2Name, player2Rating);
3898
3899                 continue;
3900             }
3901
3902             /* Improved generic start/end-of-game messages */
3903             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3904                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3905                 /* If tkind == 0: */
3906                 /* star_match[0] is the game number */
3907                 /*           [1] is the white player's name */
3908                 /*           [2] is the black player's name */
3909                 /* For end-of-game: */
3910                 /*           [3] is the reason for the game end */
3911                 /*           [4] is a PGN end game-token, preceded by " " */
3912                 /* For start-of-game: */
3913                 /*           [3] begins with "Creating" or "Continuing" */
3914                 /*           [4] is " *" or empty (don't care). */
3915                 int gamenum = atoi(star_match[0]);
3916                 char *whitename, *blackname, *why, *endtoken;
3917                 ChessMove endtype = EndOfFile;
3918
3919                 if (tkind == 0) {
3920                   whitename = star_match[1];
3921                   blackname = star_match[2];
3922                   why = star_match[3];
3923                   endtoken = star_match[4];
3924                 } else {
3925                   whitename = star_match[1];
3926                   blackname = star_match[3];
3927                   why = star_match[5];
3928                   endtoken = star_match[6];
3929                 }
3930
3931                 /* Game start messages */
3932                 if (strncmp(why, "Creating ", 9) == 0 ||
3933                     strncmp(why, "Continuing ", 11) == 0) {
3934                     gs_gamenum = gamenum;
3935                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3936                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3937                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3938 #if ZIPPY
3939                     if (appData.zippyPlay) {
3940                         ZippyGameStart(whitename, blackname);
3941                     }
3942 #endif /*ZIPPY*/
3943                     partnerBoardValid = FALSE; // [HGM] bughouse
3944                     continue;
3945                 }
3946
3947                 /* Game end messages */
3948                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3949                     ics_gamenum != gamenum) {
3950                     continue;
3951                 }
3952                 while (endtoken[0] == ' ') endtoken++;
3953                 switch (endtoken[0]) {
3954                   case '*':
3955                   default:
3956                     endtype = GameUnfinished;
3957                     break;
3958                   case '0':
3959                     endtype = BlackWins;
3960                     break;
3961                   case '1':
3962                     if (endtoken[1] == '/')
3963                       endtype = GameIsDrawn;
3964                     else
3965                       endtype = WhiteWins;
3966                     break;
3967                 }
3968                 GameEnds(endtype, why, GE_ICS);
3969 #if ZIPPY
3970                 if (appData.zippyPlay && first.initDone) {
3971                     ZippyGameEnd(endtype, why);
3972                     if (first.pr == NoProc) {
3973                       /* Start the next process early so that we'll
3974                          be ready for the next challenge */
3975                       StartChessProgram(&first);
3976                     }
3977                     /* Send "new" early, in case this command takes
3978                        a long time to finish, so that we'll be ready
3979                        for the next challenge. */
3980                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3981                     Reset(TRUE, TRUE);
3982                 }
3983 #endif /*ZIPPY*/
3984                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3985                 continue;
3986             }
3987
3988             if (looking_at(buf, &i, "Removing game * from observation") ||
3989                 looking_at(buf, &i, "no longer observing game *") ||
3990                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3991                 if (gameMode == IcsObserving &&
3992                     atoi(star_match[0]) == ics_gamenum)
3993                   {
3994                       /* icsEngineAnalyze */
3995                       if (appData.icsEngineAnalyze) {
3996                             ExitAnalyzeMode();
3997                             ModeHighlight();
3998                       }
3999                       StopClocks();
4000                       gameMode = IcsIdle;
4001                       ics_gamenum = -1;
4002                       ics_user_moved = FALSE;
4003                   }
4004                 continue;
4005             }
4006
4007             if (looking_at(buf, &i, "no longer examining game *")) {
4008                 if (gameMode == IcsExamining &&
4009                     atoi(star_match[0]) == ics_gamenum)
4010                   {
4011                       gameMode = IcsIdle;
4012                       ics_gamenum = -1;
4013                       ics_user_moved = FALSE;
4014                   }
4015                 continue;
4016             }
4017
4018             /* Advance leftover_start past any newlines we find,
4019                so only partial lines can get reparsed */
4020             if (looking_at(buf, &i, "\n")) {
4021                 prevColor = curColor;
4022                 if (curColor != ColorNormal) {
4023                     if (oldi > next_out) {
4024                         SendToPlayer(&buf[next_out], oldi - next_out);
4025                         next_out = oldi;
4026                     }
4027                     Colorize(ColorNormal, FALSE);
4028                     curColor = ColorNormal;
4029                 }
4030                 if (started == STARTED_BOARD) {
4031                     started = STARTED_NONE;
4032                     parse[parse_pos] = NULLCHAR;
4033                     ParseBoard12(parse);
4034                     ics_user_moved = 0;
4035
4036                     /* Send premove here */
4037                     if (appData.premove) {
4038                       char str[MSG_SIZ];
4039                       if (currentMove == 0 &&
4040                           gameMode == IcsPlayingWhite &&
4041                           appData.premoveWhite) {
4042                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4043                         if (appData.debugMode)
4044                           fprintf(debugFP, "Sending premove:\n");
4045                         SendToICS(str);
4046                       } else if (currentMove == 1 &&
4047                                  gameMode == IcsPlayingBlack &&
4048                                  appData.premoveBlack) {
4049                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4050                         if (appData.debugMode)
4051                           fprintf(debugFP, "Sending premove:\n");
4052                         SendToICS(str);
4053                       } else if (gotPremove) {
4054                         gotPremove = 0;
4055                         ClearPremoveHighlights();
4056                         if (appData.debugMode)
4057                           fprintf(debugFP, "Sending premove:\n");
4058                           UserMoveEvent(premoveFromX, premoveFromY,
4059                                         premoveToX, premoveToY,
4060                                         premovePromoChar);
4061                       }
4062                     }
4063
4064                     /* Usually suppress following prompt */
4065                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4066                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4067                         if (looking_at(buf, &i, "*% ")) {
4068                             savingComment = FALSE;
4069                             suppressKibitz = 0;
4070                         }
4071                     }
4072                     next_out = i;
4073                 } else if (started == STARTED_HOLDINGS) {
4074                     int gamenum;
4075                     char new_piece[MSG_SIZ];
4076                     started = STARTED_NONE;
4077                     parse[parse_pos] = NULLCHAR;
4078                     if (appData.debugMode)
4079                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4080                                                         parse, currentMove);
4081                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4082                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4083                         if (gameInfo.variant == VariantNormal) {
4084                           /* [HGM] We seem to switch variant during a game!
4085                            * Presumably no holdings were displayed, so we have
4086                            * to move the position two files to the right to
4087                            * create room for them!
4088                            */
4089                           VariantClass newVariant;
4090                           switch(gameInfo.boardWidth) { // base guess on board width
4091                                 case 9:  newVariant = VariantShogi; break;
4092                                 case 10: newVariant = VariantGreat; break;
4093                                 default: newVariant = VariantCrazyhouse; break;
4094                           }
4095                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4096                           /* Get a move list just to see the header, which
4097                              will tell us whether this is really bug or zh */
4098                           if (ics_getting_history == H_FALSE) {
4099                             ics_getting_history = H_REQUESTED;
4100                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4101                             SendToICS(str);
4102                           }
4103                         }
4104                         new_piece[0] = NULLCHAR;
4105                         sscanf(parse, "game %d white [%s black [%s <- %s",
4106                                &gamenum, white_holding, black_holding,
4107                                new_piece);
4108                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4109                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4110                         /* [HGM] copy holdings to board holdings area */
4111                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4112                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4113                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4114 #if ZIPPY
4115                         if (appData.zippyPlay && first.initDone) {
4116                             ZippyHoldings(white_holding, black_holding,
4117                                           new_piece);
4118                         }
4119 #endif /*ZIPPY*/
4120                         if (tinyLayout || smallLayout) {
4121                             char wh[16], bh[16];
4122                             PackHolding(wh, white_holding);
4123                             PackHolding(bh, black_holding);
4124                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4125                                     gameInfo.white, gameInfo.black);
4126                         } else {
4127                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4128                                     gameInfo.white, white_holding, _("vs."),
4129                                     gameInfo.black, black_holding);
4130                         }
4131                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4132                         DrawPosition(FALSE, boards[currentMove]);
4133                         DisplayTitle(str);
4134                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to partner-board holdings area */
4141                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4142                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4143                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4144                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4145                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4146                       }
4147                     }
4148                     /* Suppress following prompt */
4149                     if (looking_at(buf, &i, "*% ")) {
4150                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4151                         savingComment = FALSE;
4152                         suppressKibitz = 0;
4153                     }
4154                     next_out = i;
4155                 }
4156                 continue;
4157             }
4158
4159             i++;                /* skip unparsed character and loop back */
4160         }
4161
4162         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4163 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4164 //          SendToPlayer(&buf[next_out], i - next_out);
4165             started != STARTED_HOLDINGS && leftover_start > next_out) {
4166             SendToPlayer(&buf[next_out], leftover_start - next_out);
4167             next_out = i;
4168         }
4169
4170         leftover_len = buf_len - leftover_start;
4171         /* if buffer ends with something we couldn't parse,
4172            reparse it after appending the next read */
4173
4174     } else if (count == 0) {
4175         RemoveInputSource(isr);
4176         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4177     } else {
4178         DisplayFatalError(_("Error reading from ICS"), error, 1);
4179     }
4180 }
4181
4182
4183 /* Board style 12 looks like this:
4184
4185    <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
4186
4187  * The "<12> " is stripped before it gets to this routine.  The two
4188  * trailing 0's (flip state and clock ticking) are later addition, and
4189  * some chess servers may not have them, or may have only the first.
4190  * Additional trailing fields may be added in the future.
4191  */
4192
4193 #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"
4194
4195 #define RELATION_OBSERVING_PLAYED    0
4196 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4197 #define RELATION_PLAYING_MYMOVE      1
4198 #define RELATION_PLAYING_NOTMYMOVE  -1
4199 #define RELATION_EXAMINING           2
4200 #define RELATION_ISOLATED_BOARD     -3
4201 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4202
4203 void
4204 ParseBoard12 (char *string)
4205 {
4206 #if ZIPPY
4207     int i, takeback;
4208     char *bookHit = NULL; // [HGM] book
4209 #endif
4210     GameMode newGameMode;
4211     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4212     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4213     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4214     char to_play, board_chars[200];
4215     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4216     char black[32], white[32];
4217     Board board;
4218     int prevMove = currentMove;
4219     int ticking = 2;
4220     ChessMove moveType;
4221     int fromX, fromY, toX, toY;
4222     char promoChar;
4223     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4224     Boolean weird = FALSE, reqFlag = FALSE;
4225
4226     fromX = fromY = toX = toY = -1;
4227
4228     newGame = FALSE;
4229
4230     if (appData.debugMode)
4231       fprintf(debugFP, "Parsing board: %s\n", string);
4232
4233     move_str[0] = NULLCHAR;
4234     elapsed_time[0] = NULLCHAR;
4235     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4236         int  i = 0, j;
4237         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4238             if(string[i] == ' ') { ranks++; files = 0; }
4239             else files++;
4240             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4241             i++;
4242         }
4243         for(j = 0; j <i; j++) board_chars[j] = string[j];
4244         board_chars[i] = '\0';
4245         string += i + 1;
4246     }
4247     n = sscanf(string, PATTERN, &to_play, &double_push,
4248                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4249                &gamenum, white, black, &relation, &basetime, &increment,
4250                &white_stren, &black_stren, &white_time, &black_time,
4251                &moveNum, str, elapsed_time, move_str, &ics_flip,
4252                &ticking);
4253
4254     if (n < 21) {
4255         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4256         DisplayError(str, 0);
4257         return;
4258     }
4259
4260     /* Convert the move number to internal form */
4261     moveNum = (moveNum - 1) * 2;
4262     if (to_play == 'B') moveNum++;
4263     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4264       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4265                         0, 1);
4266       return;
4267     }
4268
4269     switch (relation) {
4270       case RELATION_OBSERVING_PLAYED:
4271       case RELATION_OBSERVING_STATIC:
4272         if (gamenum == -1) {
4273             /* Old ICC buglet */
4274             relation = RELATION_OBSERVING_STATIC;
4275         }
4276         newGameMode = IcsObserving;
4277         break;
4278       case RELATION_PLAYING_MYMOVE:
4279       case RELATION_PLAYING_NOTMYMOVE:
4280         newGameMode =
4281           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4282             IcsPlayingWhite : IcsPlayingBlack;
4283         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4284         break;
4285       case RELATION_EXAMINING:
4286         newGameMode = IcsExamining;
4287         break;
4288       case RELATION_ISOLATED_BOARD:
4289       default:
4290         /* Just display this board.  If user was doing something else,
4291            we will forget about it until the next board comes. */
4292         newGameMode = IcsIdle;
4293         break;
4294       case RELATION_STARTING_POSITION:
4295         newGameMode = gameMode;
4296         break;
4297     }
4298
4299     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4300         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4301          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4302       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4303       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4304       static int lastBgGame = -1;
4305       char *toSqr;
4306       for (k = 0; k < ranks; k++) {
4307         for (j = 0; j < files; j++)
4308           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4309         if(gameInfo.holdingsWidth > 1) {
4310              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4311              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4312         }
4313       }
4314       CopyBoard(partnerBoard, board);
4315       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4316         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4317         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4318       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4319       if(toSqr = strchr(str, '-')) {
4320         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4321         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4322       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4323       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4324       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4325       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4326       if(twoBoards) {
4327           DisplayWhiteClock(white_time*fac, to_play == 'W');
4328           DisplayBlackClock(black_time*fac, to_play != 'W');
4329           activePartner = to_play;
4330           if(gamenum != lastBgGame) {
4331               char buf[MSG_SIZ];
4332               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4333               DisplayTitle(buf);
4334           }
4335           lastBgGame = gamenum;
4336           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4337                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4338       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4339                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4340       if(!twoBoards) DisplayMessage(partnerStatus, "");
4341         partnerBoardValid = TRUE;
4342       return;
4343     }
4344
4345     if(appData.dualBoard && appData.bgObserve) {
4346         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4347             SendToICS(ics_prefix), SendToICS("pobserve\n");
4348         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4349             char buf[MSG_SIZ];
4350             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4351             SendToICS(buf);
4352         }
4353     }
4354
4355     /* Modify behavior for initial board display on move listing
4356        of wild games.
4357        */
4358     switch (ics_getting_history) {
4359       case H_FALSE:
4360       case H_REQUESTED:
4361         break;
4362       case H_GOT_REQ_HEADER:
4363       case H_GOT_UNREQ_HEADER:
4364         /* This is the initial position of the current game */
4365         gamenum = ics_gamenum;
4366         moveNum = 0;            /* old ICS bug workaround */
4367         if (to_play == 'B') {
4368           startedFromSetupPosition = TRUE;
4369           blackPlaysFirst = TRUE;
4370           moveNum = 1;
4371           if (forwardMostMove == 0) forwardMostMove = 1;
4372           if (backwardMostMove == 0) backwardMostMove = 1;
4373           if (currentMove == 0) currentMove = 1;
4374         }
4375         newGameMode = gameMode;
4376         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4377         break;
4378       case H_GOT_UNWANTED_HEADER:
4379         /* This is an initial board that we don't want */
4380         return;
4381       case H_GETTING_MOVES:
4382         /* Should not happen */
4383         DisplayError(_("Error gathering move list: extra board"), 0);
4384         ics_getting_history = H_FALSE;
4385         return;
4386     }
4387
4388    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4389                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4390                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4391      /* [HGM] We seem to have switched variant unexpectedly
4392       * Try to guess new variant from board size
4393       */
4394           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4395           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4396           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4397           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4398           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4399           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4400           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4401           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4402           /* Get a move list just to see the header, which
4403              will tell us whether this is really bug or zh */
4404           if (ics_getting_history == H_FALSE) {
4405             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4406             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4407             SendToICS(str);
4408           }
4409     }
4410
4411     /* Take action if this is the first board of a new game, or of a
4412        different game than is currently being displayed.  */
4413     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4414         relation == RELATION_ISOLATED_BOARD) {
4415
4416         /* Forget the old game and get the history (if any) of the new one */
4417         if (gameMode != BeginningOfGame) {
4418           Reset(TRUE, TRUE);
4419         }
4420         newGame = TRUE;
4421         if (appData.autoRaiseBoard) BoardToTop();
4422         prevMove = -3;
4423         if (gamenum == -1) {
4424             newGameMode = IcsIdle;
4425         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4426                    appData.getMoveList && !reqFlag) {
4427             /* Need to get game history */
4428             ics_getting_history = H_REQUESTED;
4429             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4430             SendToICS(str);
4431         }
4432
4433         /* Initially flip the board to have black on the bottom if playing
4434            black or if the ICS flip flag is set, but let the user change
4435            it with the Flip View button. */
4436         flipView = appData.autoFlipView ?
4437           (newGameMode == IcsPlayingBlack) || ics_flip :
4438           appData.flipView;
4439
4440         /* Done with values from previous mode; copy in new ones */
4441         gameMode = newGameMode;
4442         ModeHighlight();
4443         ics_gamenum = gamenum;
4444         if (gamenum == gs_gamenum) {
4445             int klen = strlen(gs_kind);
4446             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4447             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4448             gameInfo.event = StrSave(str);
4449         } else {
4450             gameInfo.event = StrSave("ICS game");
4451         }
4452         gameInfo.site = StrSave(appData.icsHost);
4453         gameInfo.date = PGNDate();
4454         gameInfo.round = StrSave("-");
4455         gameInfo.white = StrSave(white);
4456         gameInfo.black = StrSave(black);
4457         timeControl = basetime * 60 * 1000;
4458         timeControl_2 = 0;
4459         timeIncrement = increment * 1000;
4460         movesPerSession = 0;
4461         gameInfo.timeControl = TimeControlTagValue();
4462         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4463   if (appData.debugMode) {
4464     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4465     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4466     setbuf(debugFP, NULL);
4467   }
4468
4469         gameInfo.outOfBook = NULL;
4470
4471         /* Do we have the ratings? */
4472         if (strcmp(player1Name, white) == 0 &&
4473             strcmp(player2Name, black) == 0) {
4474             if (appData.debugMode)
4475               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4476                       player1Rating, player2Rating);
4477             gameInfo.whiteRating = player1Rating;
4478             gameInfo.blackRating = player2Rating;
4479         } else if (strcmp(player2Name, white) == 0 &&
4480                    strcmp(player1Name, black) == 0) {
4481             if (appData.debugMode)
4482               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4483                       player2Rating, player1Rating);
4484             gameInfo.whiteRating = player2Rating;
4485             gameInfo.blackRating = player1Rating;
4486         }
4487         player1Name[0] = player2Name[0] = NULLCHAR;
4488
4489         /* Silence shouts if requested */
4490         if (appData.quietPlay &&
4491             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4492             SendToICS(ics_prefix);
4493             SendToICS("set shout 0\n");
4494         }
4495     }
4496
4497     /* Deal with midgame name changes */
4498     if (!newGame) {
4499         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4500             if (gameInfo.white) free(gameInfo.white);
4501             gameInfo.white = StrSave(white);
4502         }
4503         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4504             if (gameInfo.black) free(gameInfo.black);
4505             gameInfo.black = StrSave(black);
4506         }
4507     }
4508
4509     /* Throw away game result if anything actually changes in examine mode */
4510     if (gameMode == IcsExamining && !newGame) {
4511         gameInfo.result = GameUnfinished;
4512         if (gameInfo.resultDetails != NULL) {
4513             free(gameInfo.resultDetails);
4514             gameInfo.resultDetails = NULL;
4515         }
4516     }
4517
4518     /* In pausing && IcsExamining mode, we ignore boards coming
4519        in if they are in a different variation than we are. */
4520     if (pauseExamInvalid) return;
4521     if (pausing && gameMode == IcsExamining) {
4522         if (moveNum <= pauseExamForwardMostMove) {
4523             pauseExamInvalid = TRUE;
4524             forwardMostMove = pauseExamForwardMostMove;
4525             return;
4526         }
4527     }
4528
4529   if (appData.debugMode) {
4530     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4531   }
4532     /* Parse the board */
4533     for (k = 0; k < ranks; k++) {
4534       for (j = 0; j < files; j++)
4535         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4536       if(gameInfo.holdingsWidth > 1) {
4537            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4538            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4539       }
4540     }
4541     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4542       board[5][BOARD_RGHT+1] = WhiteAngel;
4543       board[6][BOARD_RGHT+1] = WhiteMarshall;
4544       board[1][0] = BlackMarshall;
4545       board[2][0] = BlackAngel;
4546       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4547     }
4548     CopyBoard(boards[moveNum], board);
4549     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4550     if (moveNum == 0) {
4551         startedFromSetupPosition =
4552           !CompareBoards(board, initialPosition);
4553         if(startedFromSetupPosition)
4554             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4555     }
4556
4557     /* [HGM] Set castling rights. Take the outermost Rooks,
4558        to make it also work for FRC opening positions. Note that board12
4559        is really defective for later FRC positions, as it has no way to
4560        indicate which Rook can castle if they are on the same side of King.
4561        For the initial position we grant rights to the outermost Rooks,
4562        and remember thos rights, and we then copy them on positions
4563        later in an FRC game. This means WB might not recognize castlings with
4564        Rooks that have moved back to their original position as illegal,
4565        but in ICS mode that is not its job anyway.
4566     */
4567     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4568     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4569
4570         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4571             if(board[0][i] == WhiteRook) j = i;
4572         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4573         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4574             if(board[0][i] == WhiteRook) j = i;
4575         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4576         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4577             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4578         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4579         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4580             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4581         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4582
4583         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4584         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4585         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4587         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4588             if(board[BOARD_HEIGHT-1][k] == bKing)
4589                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4590         if(gameInfo.variant == VariantTwoKings) {
4591             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4592             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4593             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4594         }
4595     } else { int r;
4596         r = boards[moveNum][CASTLING][0] = initialRights[0];
4597         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4598         r = boards[moveNum][CASTLING][1] = initialRights[1];
4599         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4600         r = boards[moveNum][CASTLING][3] = initialRights[3];
4601         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4602         r = boards[moveNum][CASTLING][4] = initialRights[4];
4603         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4604         /* wildcastle kludge: always assume King has rights */
4605         r = boards[moveNum][CASTLING][2] = initialRights[2];
4606         r = boards[moveNum][CASTLING][5] = initialRights[5];
4607     }
4608     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4609     boards[moveNum][EP_STATUS] = EP_NONE;
4610     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4611     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4612     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4613
4614
4615     if (ics_getting_history == H_GOT_REQ_HEADER ||
4616         ics_getting_history == H_GOT_UNREQ_HEADER) {
4617         /* This was an initial position from a move list, not
4618            the current position */
4619         return;
4620     }
4621
4622     /* Update currentMove and known move number limits */
4623     newMove = newGame || moveNum > forwardMostMove;
4624
4625     if (newGame) {
4626         forwardMostMove = backwardMostMove = currentMove = moveNum;
4627         if (gameMode == IcsExamining && moveNum == 0) {
4628           /* Workaround for ICS limitation: we are not told the wild
4629              type when starting to examine a game.  But if we ask for
4630              the move list, the move list header will tell us */
4631             ics_getting_history = H_REQUESTED;
4632             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4633             SendToICS(str);
4634         }
4635     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4636                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4637 #if ZIPPY
4638         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4639         /* [HGM] applied this also to an engine that is silently watching        */
4640         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4641             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4642             gameInfo.variant == currentlyInitializedVariant) {
4643           takeback = forwardMostMove - moveNum;
4644           for (i = 0; i < takeback; i++) {
4645             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4646             SendToProgram("undo\n", &first);
4647           }
4648         }
4649 #endif
4650
4651         forwardMostMove = moveNum;
4652         if (!pausing || currentMove > forwardMostMove)
4653           currentMove = forwardMostMove;
4654     } else {
4655         /* New part of history that is not contiguous with old part */
4656         if (pausing && gameMode == IcsExamining) {
4657             pauseExamInvalid = TRUE;
4658             forwardMostMove = pauseExamForwardMostMove;
4659             return;
4660         }
4661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4662 #if ZIPPY
4663             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4664                 // [HGM] when we will receive the move list we now request, it will be
4665                 // fed to the engine from the first move on. So if the engine is not
4666                 // in the initial position now, bring it there.
4667                 InitChessProgram(&first, 0);
4668             }
4669 #endif
4670             ics_getting_history = H_REQUESTED;
4671             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4672             SendToICS(str);
4673         }
4674         forwardMostMove = backwardMostMove = currentMove = moveNum;
4675     }
4676
4677     /* Update the clocks */
4678     if (strchr(elapsed_time, '.')) {
4679       /* Time is in ms */
4680       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4681       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4682     } else {
4683       /* Time is in seconds */
4684       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4685       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4686     }
4687
4688
4689 #if ZIPPY
4690     if (appData.zippyPlay && newGame &&
4691         gameMode != IcsObserving && gameMode != IcsIdle &&
4692         gameMode != IcsExamining)
4693       ZippyFirstBoard(moveNum, basetime, increment);
4694 #endif
4695
4696     /* Put the move on the move list, first converting
4697        to canonical algebraic form. */
4698     if (moveNum > 0) {
4699   if (appData.debugMode) {
4700     int f = forwardMostMove;
4701     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4702             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4703             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4704     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4705     fprintf(debugFP, "moveNum = %d\n", moveNum);
4706     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4707     setbuf(debugFP, NULL);
4708   }
4709         if (moveNum <= backwardMostMove) {
4710             /* We don't know what the board looked like before
4711                this move.  Punt. */
4712           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             moveList[moveNum - 1][0] = NULLCHAR;
4716         } else if (strcmp(move_str, "none") == 0) {
4717             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4718             /* Again, we don't know what the board looked like;
4719                this is really the start of the game. */
4720             parseList[moveNum - 1][0] = NULLCHAR;
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722             backwardMostMove = moveNum;
4723             startedFromSetupPosition = TRUE;
4724             fromX = fromY = toX = toY = -1;
4725         } else {
4726           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4727           //                 So we parse the long-algebraic move string in stead of the SAN move
4728           int valid; char buf[MSG_SIZ], *prom;
4729
4730           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4731                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4732           // str looks something like "Q/a1-a2"; kill the slash
4733           if(str[1] == '/')
4734             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4735           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4736           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4737                 strcat(buf, prom); // long move lacks promo specification!
4738           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4739                 if(appData.debugMode)
4740                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4741                 safeStrCpy(move_str, buf, MSG_SIZ);
4742           }
4743           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4744                                 &fromX, &fromY, &toX, &toY, &promoChar)
4745                || ParseOneMove(buf, moveNum - 1, &moveType,
4746                                 &fromX, &fromY, &toX, &toY, &promoChar);
4747           // end of long SAN patch
4748           if (valid) {
4749             (void) CoordsToAlgebraic(boards[moveNum - 1],
4750                                      PosFlags(moveNum - 1),
4751                                      fromY, fromX, toY, toX, promoChar,
4752                                      parseList[moveNum-1]);
4753             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4754               case MT_NONE:
4755               case MT_STALEMATE:
4756               default:
4757                 break;
4758               case MT_CHECK:
4759                 if(gameInfo.variant != VariantShogi)
4760                     strcat(parseList[moveNum - 1], "+");
4761                 break;
4762               case MT_CHECKMATE:
4763               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4764                 strcat(parseList[moveNum - 1], "#");
4765                 break;
4766             }
4767             strcat(parseList[moveNum - 1], " ");
4768             strcat(parseList[moveNum - 1], elapsed_time);
4769             /* currentMoveString is set as a side-effect of ParseOneMove */
4770             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4771             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4772             strcat(moveList[moveNum - 1], "\n");
4773
4774             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4775                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4776               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4777                 ChessSquare old, new = boards[moveNum][k][j];
4778                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4779                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4780                   if(old == new) continue;
4781                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4782                   else if(new == WhiteWazir || new == BlackWazir) {
4783                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4784                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4785                       else boards[moveNum][k][j] = old; // preserve type of Gold
4786                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4787                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4788               }
4789           } else {
4790             /* Move from ICS was illegal!?  Punt. */
4791             if (appData.debugMode) {
4792               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4793               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4794             }
4795             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4796             strcat(parseList[moveNum - 1], " ");
4797             strcat(parseList[moveNum - 1], elapsed_time);
4798             moveList[moveNum - 1][0] = NULLCHAR;
4799             fromX = fromY = toX = toY = -1;
4800           }
4801         }
4802   if (appData.debugMode) {
4803     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4804     setbuf(debugFP, NULL);
4805   }
4806
4807 #if ZIPPY
4808         /* Send move to chess program (BEFORE animating it). */
4809         if (appData.zippyPlay && !newGame && newMove &&
4810            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4811
4812             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4813                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4814                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4815                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4816                             move_str);
4817                     DisplayError(str, 0);
4818                 } else {
4819                     if (first.sendTime) {
4820                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4821                     }
4822                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4823                     if (firstMove && !bookHit) {
4824                         firstMove = FALSE;
4825                         if (first.useColors) {
4826                           SendToProgram(gameMode == IcsPlayingWhite ?
4827                                         "white\ngo\n" :
4828                                         "black\ngo\n", &first);
4829                         } else {
4830                           SendToProgram("go\n", &first);
4831                         }
4832                         first.maybeThinking = TRUE;
4833                     }
4834                 }
4835             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4836               if (moveList[moveNum - 1][0] == NULLCHAR) {
4837                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4838                 DisplayError(str, 0);
4839               } else {
4840                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4841                 SendMoveToProgram(moveNum - 1, &first);
4842               }
4843             }
4844         }
4845 #endif
4846     }
4847
4848     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4849         /* If move comes from a remote source, animate it.  If it
4850            isn't remote, it will have already been animated. */
4851         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4852             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4853         }
4854         if (!pausing && appData.highlightLastMove) {
4855             SetHighlights(fromX, fromY, toX, toY);
4856         }
4857     }
4858
4859     /* Start the clocks */
4860     whiteFlag = blackFlag = FALSE;
4861     appData.clockMode = !(basetime == 0 && increment == 0);
4862     if (ticking == 0) {
4863       ics_clock_paused = TRUE;
4864       StopClocks();
4865     } else if (ticking == 1) {
4866       ics_clock_paused = FALSE;
4867     }
4868     if (gameMode == IcsIdle ||
4869         relation == RELATION_OBSERVING_STATIC ||
4870         relation == RELATION_EXAMINING ||
4871         ics_clock_paused)
4872       DisplayBothClocks();
4873     else
4874       StartClocks();
4875
4876     /* Display opponents and material strengths */
4877     if (gameInfo.variant != VariantBughouse &&
4878         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4879         if (tinyLayout || smallLayout) {
4880             if(gameInfo.variant == VariantNormal)
4881               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4882                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4883                     basetime, increment);
4884             else
4885               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4886                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4887                     basetime, increment, (int) gameInfo.variant);
4888         } else {
4889             if(gameInfo.variant == VariantNormal)
4890               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4891                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4892                     basetime, increment);
4893             else
4894               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4895                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4896                     basetime, increment, VariantName(gameInfo.variant));
4897         }
4898         DisplayTitle(str);
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4901   }
4902     }
4903
4904
4905     /* Display the board */
4906     if (!pausing && !appData.noGUI) {
4907
4908       if (appData.premove)
4909           if (!gotPremove ||
4910              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4911              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4912               ClearPremoveHighlights();
4913
4914       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4915         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4916       DrawPosition(j, boards[currentMove]);
4917
4918       DisplayMove(moveNum - 1);
4919       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4920             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4921               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4922         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4923       }
4924     }
4925
4926     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4927 #if ZIPPY
4928     if(bookHit) { // [HGM] book: simulate book reply
4929         static char bookMove[MSG_SIZ]; // a bit generous?
4930
4931         programStats.nodes = programStats.depth = programStats.time =
4932         programStats.score = programStats.got_only_move = 0;
4933         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4934
4935         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4936         strcat(bookMove, bookHit);
4937         HandleMachineMove(bookMove, &first);
4938     }
4939 #endif
4940 }
4941
4942 void
4943 GetMoveListEvent ()
4944 {
4945     char buf[MSG_SIZ];
4946     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4947         ics_getting_history = H_REQUESTED;
4948         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4949         SendToICS(buf);
4950     }
4951 }
4952
4953 void
4954 SendToBoth (char *msg)
4955 {   // to make it easy to keep two engines in step in dual analysis
4956     SendToProgram(msg, &first);
4957     if(second.analyzing) SendToProgram(msg, &second);
4958 }
4959
4960 void
4961 AnalysisPeriodicEvent (int force)
4962 {
4963     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4964          && !force) || !appData.periodicUpdates)
4965       return;
4966
4967     /* Send . command to Crafty to collect stats */
4968     SendToBoth(".\n");
4969
4970     /* Don't send another until we get a response (this makes
4971        us stop sending to old Crafty's which don't understand
4972        the "." command (sending illegal cmds resets node count & time,
4973        which looks bad)) */
4974     programStats.ok_to_send = 0;
4975 }
4976
4977 void
4978 ics_update_width (int new_width)
4979 {
4980         ics_printf("set width %d\n", new_width);
4981 }
4982
4983 void
4984 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4985 {
4986     char buf[MSG_SIZ];
4987
4988     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4989         // null move in variant where engine does not understand it (for analysis purposes)
4990         SendBoard(cps, moveNum + 1); // send position after move in stead.
4991         return;
4992     }
4993     if (cps->useUsermove) {
4994       SendToProgram("usermove ", cps);
4995     }
4996     if (cps->useSAN) {
4997       char *space;
4998       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4999         int len = space - parseList[moveNum];
5000         memcpy(buf, parseList[moveNum], len);
5001         buf[len++] = '\n';
5002         buf[len] = NULLCHAR;
5003       } else {
5004         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5005       }
5006       SendToProgram(buf, cps);
5007     } else {
5008       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5009         AlphaRank(moveList[moveNum], 4);
5010         SendToProgram(moveList[moveNum], cps);
5011         AlphaRank(moveList[moveNum], 4); // and back
5012       } else
5013       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5014        * the engine. It would be nice to have a better way to identify castle
5015        * moves here. */
5016       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5017                                                                          && cps->useOOCastle) {
5018         int fromX = moveList[moveNum][0] - AAA;
5019         int fromY = moveList[moveNum][1] - ONE;
5020         int toX = moveList[moveNum][2] - AAA;
5021         int toY = moveList[moveNum][3] - ONE;
5022         if((boards[moveNum][fromY][fromX] == WhiteKing
5023             && boards[moveNum][toY][toX] == WhiteRook)
5024            || (boards[moveNum][fromY][fromX] == BlackKing
5025                && boards[moveNum][toY][toX] == BlackRook)) {
5026           if(toX > fromX) SendToProgram("O-O\n", cps);
5027           else SendToProgram("O-O-O\n", cps);
5028         }
5029         else SendToProgram(moveList[moveNum], cps);
5030       } else
5031       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5032         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5033           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5034           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5035                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5036         } else
5037           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5038                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5039         SendToProgram(buf, cps);
5040       }
5041       else SendToProgram(moveList[moveNum], cps);
5042       /* End of additions by Tord */
5043     }
5044
5045     /* [HGM] setting up the opening has brought engine in force mode! */
5046     /*       Send 'go' if we are in a mode where machine should play. */
5047     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5048         (gameMode == TwoMachinesPlay   ||
5049 #if ZIPPY
5050          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5051 #endif
5052          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5053         SendToProgram("go\n", cps);
5054   if (appData.debugMode) {
5055     fprintf(debugFP, "(extra)\n");
5056   }
5057     }
5058     setboardSpoiledMachineBlack = 0;
5059 }
5060
5061 void
5062 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5063 {
5064     char user_move[MSG_SIZ];
5065     char suffix[4];
5066
5067     if(gameInfo.variant == VariantSChess && promoChar) {
5068         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5069         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5070     } else suffix[0] = NULLCHAR;
5071
5072     switch (moveType) {
5073       default:
5074         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5075                 (int)moveType, fromX, fromY, toX, toY);
5076         DisplayError(user_move + strlen("say "), 0);
5077         break;
5078       case WhiteKingSideCastle:
5079       case BlackKingSideCastle:
5080       case WhiteQueenSideCastleWild:
5081       case BlackQueenSideCastleWild:
5082       /* PUSH Fabien */
5083       case WhiteHSideCastleFR:
5084       case BlackHSideCastleFR:
5085       /* POP Fabien */
5086         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5087         break;
5088       case WhiteQueenSideCastle:
5089       case BlackQueenSideCastle:
5090       case WhiteKingSideCastleWild:
5091       case BlackKingSideCastleWild:
5092       /* PUSH Fabien */
5093       case WhiteASideCastleFR:
5094       case BlackASideCastleFR:
5095       /* POP Fabien */
5096         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5097         break;
5098       case WhiteNonPromotion:
5099       case BlackNonPromotion:
5100         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5101         break;
5102       case WhitePromotion:
5103       case BlackPromotion:
5104         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5105            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5106           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5107                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5108                 PieceToChar(WhiteFerz));
5109         else if(gameInfo.variant == VariantGreat)
5110           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5111                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5112                 PieceToChar(WhiteMan));
5113         else
5114           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5115                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5116                 promoChar);
5117         break;
5118       case WhiteDrop:
5119       case BlackDrop:
5120       drop:
5121         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5122                  ToUpper(PieceToChar((ChessSquare) fromX)),
5123                  AAA + toX, ONE + toY);
5124         break;
5125       case IllegalMove:  /* could be a variant we don't quite understand */
5126         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5127       case NormalMove:
5128       case WhiteCapturesEnPassant:
5129       case BlackCapturesEnPassant:
5130         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5131                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5132         break;
5133     }
5134     SendToICS(user_move);
5135     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5136         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5137 }
5138
5139 void
5140 UploadGameEvent ()
5141 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5142     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5143     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5144     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5145       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5146       return;
5147     }
5148     if(gameMode != IcsExamining) { // is this ever not the case?
5149         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5150
5151         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5152           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5153         } else { // on FICS we must first go to general examine mode
5154           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5155         }
5156         if(gameInfo.variant != VariantNormal) {
5157             // try figure out wild number, as xboard names are not always valid on ICS
5158             for(i=1; i<=36; i++) {
5159               snprintf(buf, MSG_SIZ, "wild/%d", i);
5160                 if(StringToVariant(buf) == gameInfo.variant) break;
5161             }
5162             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5163             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5164             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5165         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5166         SendToICS(ics_prefix);
5167         SendToICS(buf);
5168         if(startedFromSetupPosition || backwardMostMove != 0) {
5169           fen = PositionToFEN(backwardMostMove, NULL, 1);
5170           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5171             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5172             SendToICS(buf);
5173           } else { // FICS: everything has to set by separate bsetup commands
5174             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5175             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5176             SendToICS(buf);
5177             if(!WhiteOnMove(backwardMostMove)) {
5178                 SendToICS("bsetup tomove black\n");
5179             }
5180             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5181             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5182             SendToICS(buf);
5183             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5184             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5185             SendToICS(buf);
5186             i = boards[backwardMostMove][EP_STATUS];
5187             if(i >= 0) { // set e.p.
5188               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5189                 SendToICS(buf);
5190             }
5191             bsetup++;
5192           }
5193         }
5194       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5195             SendToICS("bsetup done\n"); // switch to normal examining.
5196     }
5197     for(i = backwardMostMove; i<last; i++) {
5198         char buf[20];
5199         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5200         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5201             int len = strlen(moveList[i]);
5202             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5203             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5204         }
5205         SendToICS(buf);
5206     }
5207     SendToICS(ics_prefix);
5208     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5209 }
5210
5211 void
5212 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5213 {
5214     if (rf == DROP_RANK) {
5215       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5216       sprintf(move, "%c@%c%c\n",
5217                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5218     } else {
5219         if (promoChar == 'x' || promoChar == NULLCHAR) {
5220           sprintf(move, "%c%c%c%c\n",
5221                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5222         } else {
5223             sprintf(move, "%c%c%c%c%c\n",
5224                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5225         }
5226     }
5227 }
5228
5229 void
5230 ProcessICSInitScript (FILE *f)
5231 {
5232     char buf[MSG_SIZ];
5233
5234     while (fgets(buf, MSG_SIZ, f)) {
5235         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5236     }
5237
5238     fclose(f);
5239 }
5240
5241
5242 static int lastX, lastY, selectFlag, dragging;
5243
5244 void
5245 Sweep (int step)
5246 {
5247     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5248     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5249     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5250     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5251     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5252     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5253     do {
5254         promoSweep -= step;
5255         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5256         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5257         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5258         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5259         if(!step) step = -1;
5260     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5261             appData.testLegality && (promoSweep == king ||
5262             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5263     if(toX >= 0) {
5264         int victim = boards[currentMove][toY][toX];
5265         boards[currentMove][toY][toX] = promoSweep;
5266         DrawPosition(FALSE, boards[currentMove]);
5267         boards[currentMove][toY][toX] = victim;
5268     } else
5269     ChangeDragPiece(promoSweep);
5270 }
5271
5272 int
5273 PromoScroll (int x, int y)
5274 {
5275   int step = 0;
5276
5277   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5278   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5279   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5280   if(!step) return FALSE;
5281   lastX = x; lastY = y;
5282   if((promoSweep < BlackPawn) == flipView) step = -step;
5283   if(step > 0) selectFlag = 1;
5284   if(!selectFlag) Sweep(step);
5285   return FALSE;
5286 }
5287
5288 void
5289 NextPiece (int step)
5290 {
5291     ChessSquare piece = boards[currentMove][toY][toX];
5292     do {
5293         pieceSweep -= step;
5294         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5295         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5296         if(!step) step = -1;
5297     } while(PieceToChar(pieceSweep) == '.');
5298     boards[currentMove][toY][toX] = pieceSweep;
5299     DrawPosition(FALSE, boards[currentMove]);
5300     boards[currentMove][toY][toX] = piece;
5301 }
5302 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5303 void
5304 AlphaRank (char *move, int n)
5305 {
5306 //    char *p = move, c; int x, y;
5307
5308     if (appData.debugMode) {
5309         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5310     }
5311
5312     if(move[1]=='*' &&
5313        move[2]>='0' && move[2]<='9' &&
5314        move[3]>='a' && move[3]<='x'    ) {
5315         move[1] = '@';
5316         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5317         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5318     } else
5319     if(move[0]>='0' && move[0]<='9' &&
5320        move[1]>='a' && move[1]<='x' &&
5321        move[2]>='0' && move[2]<='9' &&
5322        move[3]>='a' && move[3]<='x'    ) {
5323         /* input move, Shogi -> normal */
5324         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5325         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5326         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5327         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5328     } else
5329     if(move[1]=='@' &&
5330        move[3]>='0' && move[3]<='9' &&
5331        move[2]>='a' && move[2]<='x'    ) {
5332         move[1] = '*';
5333         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5334         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5335     } else
5336     if(
5337        move[0]>='a' && move[0]<='x' &&
5338        move[3]>='0' && move[3]<='9' &&
5339        move[2]>='a' && move[2]<='x'    ) {
5340          /* output move, normal -> Shogi */
5341         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5342         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5343         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5344         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5345         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5346     }
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "   out = '%s'\n", move);
5349     }
5350 }
5351
5352 char yy_textstr[8000];
5353
5354 /* Parser for moves from gnuchess, ICS, or user typein box */
5355 Boolean
5356 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5357 {
5358     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5359
5360     switch (*moveType) {
5361       case WhitePromotion:
5362       case BlackPromotion:
5363       case WhiteNonPromotion:
5364       case BlackNonPromotion:
5365       case NormalMove:
5366       case WhiteCapturesEnPassant:
5367       case BlackCapturesEnPassant:
5368       case WhiteKingSideCastle:
5369       case WhiteQueenSideCastle:
5370       case BlackKingSideCastle:
5371       case BlackQueenSideCastle:
5372       case WhiteKingSideCastleWild:
5373       case WhiteQueenSideCastleWild:
5374       case BlackKingSideCastleWild:
5375       case BlackQueenSideCastleWild:
5376       /* Code added by Tord: */
5377       case WhiteHSideCastleFR:
5378       case WhiteASideCastleFR:
5379       case BlackHSideCastleFR:
5380       case BlackASideCastleFR:
5381       /* End of code added by Tord */
5382       case IllegalMove:         /* bug or odd chess variant */
5383         *fromX = currentMoveString[0] - AAA;
5384         *fromY = currentMoveString[1] - ONE;
5385         *toX = currentMoveString[2] - AAA;
5386         *toY = currentMoveString[3] - ONE;
5387         *promoChar = currentMoveString[4];
5388         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5389             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5390     if (appData.debugMode) {
5391         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5392     }
5393             *fromX = *fromY = *toX = *toY = 0;
5394             return FALSE;
5395         }
5396         if (appData.testLegality) {
5397           return (*moveType != IllegalMove);
5398         } else {
5399           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5400                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5401         }
5402
5403       case WhiteDrop:
5404       case BlackDrop:
5405         *fromX = *moveType == WhiteDrop ?
5406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5407           (int) CharToPiece(ToLower(currentMoveString[0]));
5408         *fromY = DROP_RANK;
5409         *toX = currentMoveString[2] - AAA;
5410         *toY = currentMoveString[3] - ONE;
5411         *promoChar = NULLCHAR;
5412         return TRUE;
5413
5414       case AmbiguousMove:
5415       case ImpossibleMove:
5416       case EndOfFile:
5417       case ElapsedTime:
5418       case Comment:
5419       case PGNTag:
5420       case NAG:
5421       case WhiteWins:
5422       case BlackWins:
5423       case GameIsDrawn:
5424       default:
5425     if (appData.debugMode) {
5426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5427     }
5428         /* bug? */
5429         *fromX = *fromY = *toX = *toY = 0;
5430         *promoChar = NULLCHAR;
5431         return FALSE;
5432     }
5433 }
5434
5435 Boolean pushed = FALSE;
5436 char *lastParseAttempt;
5437
5438 void
5439 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5440 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5441   int fromX, fromY, toX, toY; char promoChar;
5442   ChessMove moveType;
5443   Boolean valid;
5444   int nr = 0;
5445
5446   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5447   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5448     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5449     pushed = TRUE;
5450   }
5451   endPV = forwardMostMove;
5452   do {
5453     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5454     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5455     lastParseAttempt = pv;
5456     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5457     if(!valid && nr == 0 &&
5458        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5459         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5460         // Hande case where played move is different from leading PV move
5461         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5462         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5463         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5464         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5465           endPV += 2; // if position different, keep this
5466           moveList[endPV-1][0] = fromX + AAA;
5467           moveList[endPV-1][1] = fromY + ONE;
5468           moveList[endPV-1][2] = toX + AAA;
5469           moveList[endPV-1][3] = toY + ONE;
5470           parseList[endPV-1][0] = NULLCHAR;
5471           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5472         }
5473       }
5474     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5475     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5476     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5477     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5478         valid++; // allow comments in PV
5479         continue;
5480     }
5481     nr++;
5482     if(endPV+1 > framePtr) break; // no space, truncate
5483     if(!valid) break;
5484     endPV++;
5485     CopyBoard(boards[endPV], boards[endPV-1]);
5486     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5487     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5488     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5489     CoordsToAlgebraic(boards[endPV - 1],
5490                              PosFlags(endPV - 1),
5491                              fromY, fromX, toY, toX, promoChar,
5492                              parseList[endPV - 1]);
5493   } while(valid);
5494   if(atEnd == 2) return; // used hidden, for PV conversion
5495   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499   DrawPosition(TRUE, boards[currentMove]);
5500 }
5501
5502 int
5503 MultiPV (ChessProgramState *cps)
5504 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5505         int i;
5506         for(i=0; i<cps->nrOptions; i++)
5507             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5508                 return i;
5509         return -1;
5510 }
5511
5512 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5513
5514 Boolean
5515 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5516 {
5517         int startPV, multi, lineStart, origIndex = index;
5518         char *p, buf2[MSG_SIZ];
5519         ChessProgramState *cps = (pane ? &second : &first);
5520
5521         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5522         lastX = x; lastY = y;
5523         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5524         lineStart = startPV = index;
5525         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5526         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5527         index = startPV;
5528         do{ while(buf[index] && buf[index] != '\n') index++;
5529         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5530         buf[index] = 0;
5531         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5532                 int n = cps->option[multi].value;
5533                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5534                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5535                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5536                 cps->option[multi].value = n;
5537                 *start = *end = 0;
5538                 return FALSE;
5539         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5540                 ExcludeClick(origIndex - lineStart);
5541                 return FALSE;
5542         }
5543         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5544         *start = startPV; *end = index-1;
5545         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5546         return TRUE;
5547 }
5548
5549 char *
5550 PvToSAN (char *pv)
5551 {
5552         static char buf[10*MSG_SIZ];
5553         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5554         *buf = NULLCHAR;
5555         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5556         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5557         for(i = forwardMostMove; i<endPV; i++){
5558             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5559             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5560             k += strlen(buf+k);
5561         }
5562         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5563         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5564         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5565         endPV = savedEnd;
5566         return buf;
5567 }
5568
5569 Boolean
5570 LoadPV (int x, int y)
5571 { // called on right mouse click to load PV
5572   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5573   lastX = x; lastY = y;
5574   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5575   extendGame = FALSE;
5576   return TRUE;
5577 }
5578
5579 void
5580 UnLoadPV ()
5581 {
5582   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5583   if(endPV < 0) return;
5584   if(appData.autoCopyPV) CopyFENToClipboard();
5585   endPV = -1;
5586   if(extendGame && currentMove > forwardMostMove) {
5587         Boolean saveAnimate = appData.animate;
5588         if(pushed) {
5589             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5590                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5591             } else storedGames--; // abandon shelved tail of original game
5592         }
5593         pushed = FALSE;
5594         forwardMostMove = currentMove;
5595         currentMove = oldFMM;
5596         appData.animate = FALSE;
5597         ToNrEvent(forwardMostMove);
5598         appData.animate = saveAnimate;
5599   }
5600   currentMove = forwardMostMove;
5601   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5602   ClearPremoveHighlights();
5603   DrawPosition(TRUE, boards[currentMove]);
5604 }
5605
5606 void
5607 MovePV (int x, int y, int h)
5608 { // step through PV based on mouse coordinates (called on mouse move)
5609   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5610
5611   // we must somehow check if right button is still down (might be released off board!)
5612   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5613   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5614   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5615   if(!step) return;
5616   lastX = x; lastY = y;
5617
5618   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5619   if(endPV < 0) return;
5620   if(y < margin) step = 1; else
5621   if(y > h - margin) step = -1;
5622   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5623   currentMove += step;
5624   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5625   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5626                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5627   DrawPosition(FALSE, boards[currentMove]);
5628 }
5629
5630
5631 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5632 // All positions will have equal probability, but the current method will not provide a unique
5633 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5634 #define DARK 1
5635 #define LITE 2
5636 #define ANY 3
5637
5638 int squaresLeft[4];
5639 int piecesLeft[(int)BlackPawn];
5640 int seed, nrOfShuffles;
5641
5642 void
5643 GetPositionNumber ()
5644 {       // sets global variable seed
5645         int i;
5646
5647         seed = appData.defaultFrcPosition;
5648         if(seed < 0) { // randomize based on time for negative FRC position numbers
5649                 for(i=0; i<50; i++) seed += random();
5650                 seed = random() ^ random() >> 8 ^ random() << 8;
5651                 if(seed<0) seed = -seed;
5652         }
5653 }
5654
5655 int
5656 put (Board board, int pieceType, int rank, int n, int shade)
5657 // put the piece on the (n-1)-th empty squares of the given shade
5658 {
5659         int i;
5660
5661         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5662                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5663                         board[rank][i] = (ChessSquare) pieceType;
5664                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5665                         squaresLeft[ANY]--;
5666                         piecesLeft[pieceType]--;
5667                         return i;
5668                 }
5669         }
5670         return -1;
5671 }
5672
5673
5674 void
5675 AddOnePiece (Board board, int pieceType, int rank, int shade)
5676 // calculate where the next piece goes, (any empty square), and put it there
5677 {
5678         int i;
5679
5680         i = seed % squaresLeft[shade];
5681         nrOfShuffles *= squaresLeft[shade];
5682         seed /= squaresLeft[shade];
5683         put(board, pieceType, rank, i, shade);
5684 }
5685
5686 void
5687 AddTwoPieces (Board board, int pieceType, int rank)
5688 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5689 {
5690         int i, n=squaresLeft[ANY], j=n-1, k;
5691
5692         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5693         i = seed % k;  // pick one
5694         nrOfShuffles *= k;
5695         seed /= k;
5696         while(i >= j) i -= j--;
5697         j = n - 1 - j; i += j;
5698         put(board, pieceType, rank, j, ANY);
5699         put(board, pieceType, rank, i, ANY);
5700 }
5701
5702 void
5703 SetUpShuffle (Board board, int number)
5704 {
5705         int i, p, first=1;
5706
5707         GetPositionNumber(); nrOfShuffles = 1;
5708
5709         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5710         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5711         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5712
5713         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5714
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5716             p = (int) board[0][i];
5717             if(p < (int) BlackPawn) piecesLeft[p] ++;
5718             board[0][i] = EmptySquare;
5719         }
5720
5721         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5722             // shuffles restricted to allow normal castling put KRR first
5723             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5724                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5725             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5726                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5727             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5728                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5729             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5730                 put(board, WhiteRook, 0, 0, ANY);
5731             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5732         }
5733
5734         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5735             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5736             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5737                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5738                 while(piecesLeft[p] >= 2) {
5739                     AddOnePiece(board, p, 0, LITE);
5740                     AddOnePiece(board, p, 0, DARK);
5741                 }
5742                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5743             }
5744
5745         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5746             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5747             // but we leave King and Rooks for last, to possibly obey FRC restriction
5748             if(p == (int)WhiteRook) continue;
5749             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5750             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5751         }
5752
5753         // now everything is placed, except perhaps King (Unicorn) and Rooks
5754
5755         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5756             // Last King gets castling rights
5757             while(piecesLeft[(int)WhiteUnicorn]) {
5758                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5759                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5760             }
5761
5762             while(piecesLeft[(int)WhiteKing]) {
5763                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5764                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5765             }
5766
5767
5768         } else {
5769             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5770             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5771         }
5772
5773         // Only Rooks can be left; simply place them all
5774         while(piecesLeft[(int)WhiteRook]) {
5775                 i = put(board, WhiteRook, 0, 0, ANY);
5776                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5777                         if(first) {
5778                                 first=0;
5779                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5780                         }
5781                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5782                 }
5783         }
5784         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5785             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5786         }
5787
5788         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5789 }
5790
5791 int
5792 SetCharTable (char *table, const char * map)
5793 /* [HGM] moved here from winboard.c because of its general usefulness */
5794 /*       Basically a safe strcpy that uses the last character as King */
5795 {
5796     int result = FALSE; int NrPieces;
5797
5798     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5799                     && NrPieces >= 12 && !(NrPieces&1)) {
5800         int i; /* [HGM] Accept even length from 12 to 34 */
5801
5802         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5803         for( i=0; i<NrPieces/2-1; i++ ) {
5804             table[i] = map[i];
5805             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5806         }
5807         table[(int) WhiteKing]  = map[NrPieces/2-1];
5808         table[(int) BlackKing]  = map[NrPieces-1];
5809
5810         result = TRUE;
5811     }
5812
5813     return result;
5814 }
5815
5816 void
5817 Prelude (Board board)
5818 {       // [HGM] superchess: random selection of exo-pieces
5819         int i, j, k; ChessSquare p;
5820         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5821
5822         GetPositionNumber(); // use FRC position number
5823
5824         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5825             SetCharTable(pieceToChar, appData.pieceToCharTable);
5826             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5827                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5828         }
5829
5830         j = seed%4;                 seed /= 4;
5831         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5832         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5833         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5834         j = seed%3 + (seed%3 >= j); seed /= 3;
5835         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5836         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5837         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5838         j = seed%3;                 seed /= 3;
5839         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5840         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5841         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5842         j = seed%2 + (seed%2 >= j); seed /= 2;
5843         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5844         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5845         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5846         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5847         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5848         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5849         put(board, exoPieces[0],    0, 0, ANY);
5850         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5851 }
5852
5853 void
5854 InitPosition (int redraw)
5855 {
5856     ChessSquare (* pieces)[BOARD_FILES];
5857     int i, j, pawnRow, overrule,
5858     oldx = gameInfo.boardWidth,
5859     oldy = gameInfo.boardHeight,
5860     oldh = gameInfo.holdingsWidth;
5861     static int oldv;
5862
5863     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5864
5865     /* [AS] Initialize pv info list [HGM] and game status */
5866     {
5867         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5868             pvInfoList[i].depth = 0;
5869             boards[i][EP_STATUS] = EP_NONE;
5870             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5871         }
5872
5873         initialRulePlies = 0; /* 50-move counter start */
5874
5875         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5876         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5877     }
5878
5879
5880     /* [HGM] logic here is completely changed. In stead of full positions */
5881     /* the initialized data only consist of the two backranks. The switch */
5882     /* selects which one we will use, which is than copied to the Board   */
5883     /* initialPosition, which for the rest is initialized by Pawns and    */
5884     /* empty squares. This initial position is then copied to boards[0],  */
5885     /* possibly after shuffling, so that it remains available.            */
5886
5887     gameInfo.holdingsWidth = 0; /* default board sizes */
5888     gameInfo.boardWidth    = 8;
5889     gameInfo.boardHeight   = 8;
5890     gameInfo.holdingsSize  = 0;
5891     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5892     for(i=0; i<BOARD_FILES-2; i++)
5893       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5894     initialPosition[EP_STATUS] = EP_NONE;
5895     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5896     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5897          SetCharTable(pieceNickName, appData.pieceNickNames);
5898     else SetCharTable(pieceNickName, "............");
5899     pieces = FIDEArray;
5900
5901     switch (gameInfo.variant) {
5902     case VariantFischeRandom:
5903       shuffleOpenings = TRUE;
5904     default:
5905       break;
5906     case VariantShatranj:
5907       pieces = ShatranjArray;
5908       nrCastlingRights = 0;
5909       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5910       break;
5911     case VariantMakruk:
5912       pieces = makrukArray;
5913       nrCastlingRights = 0;
5914       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5915       break;
5916     case VariantASEAN:
5917       pieces = aseanArray;
5918       nrCastlingRights = 0;
5919       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5920       break;
5921     case VariantTwoKings:
5922       pieces = twoKingsArray;
5923       break;
5924     case VariantGrand:
5925       pieces = GrandArray;
5926       nrCastlingRights = 0;
5927       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5928       gameInfo.boardWidth = 10;
5929       gameInfo.boardHeight = 10;
5930       gameInfo.holdingsSize = 7;
5931       break;
5932     case VariantCapaRandom:
5933       shuffleOpenings = TRUE;
5934     case VariantCapablanca:
5935       pieces = CapablancaArray;
5936       gameInfo.boardWidth = 10;
5937       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5938       break;
5939     case VariantGothic:
5940       pieces = GothicArray;
5941       gameInfo.boardWidth = 10;
5942       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5943       break;
5944     case VariantSChess:
5945       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5946       gameInfo.holdingsSize = 7;
5947       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5948       break;
5949     case VariantJanus:
5950       pieces = JanusArray;
5951       gameInfo.boardWidth = 10;
5952       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5953       nrCastlingRights = 6;
5954         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5955         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5956         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5957         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5958         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5959         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5960       break;
5961     case VariantFalcon:
5962       pieces = FalconArray;
5963       gameInfo.boardWidth = 10;
5964       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5965       break;
5966     case VariantXiangqi:
5967       pieces = XiangqiArray;
5968       gameInfo.boardWidth  = 9;
5969       gameInfo.boardHeight = 10;
5970       nrCastlingRights = 0;
5971       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5972       break;
5973     case VariantShogi:
5974       pieces = ShogiArray;
5975       gameInfo.boardWidth  = 9;
5976       gameInfo.boardHeight = 9;
5977       gameInfo.holdingsSize = 7;
5978       nrCastlingRights = 0;
5979       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5980       break;
5981     case VariantCourier:
5982       pieces = CourierArray;
5983       gameInfo.boardWidth  = 12;
5984       nrCastlingRights = 0;
5985       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5986       break;
5987     case VariantKnightmate:
5988       pieces = KnightmateArray;
5989       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5990       break;
5991     case VariantSpartan:
5992       pieces = SpartanArray;
5993       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5994       break;
5995     case VariantFairy:
5996       pieces = fairyArray;
5997       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5998       break;
5999     case VariantGreat:
6000       pieces = GreatArray;
6001       gameInfo.boardWidth = 10;
6002       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6003       gameInfo.holdingsSize = 8;
6004       break;
6005     case VariantSuper:
6006       pieces = FIDEArray;
6007       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6008       gameInfo.holdingsSize = 8;
6009       startedFromSetupPosition = TRUE;
6010       break;
6011     case VariantCrazyhouse:
6012     case VariantBughouse:
6013       pieces = FIDEArray;
6014       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6015       gameInfo.holdingsSize = 5;
6016       break;
6017     case VariantWildCastle:
6018       pieces = FIDEArray;
6019       /* !!?shuffle with kings guaranteed to be on d or e file */
6020       shuffleOpenings = 1;
6021       break;
6022     case VariantNoCastle:
6023       pieces = FIDEArray;
6024       nrCastlingRights = 0;
6025       /* !!?unconstrained back-rank shuffle */
6026       shuffleOpenings = 1;
6027       break;
6028     }
6029
6030     overrule = 0;
6031     if(appData.NrFiles >= 0) {
6032         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6033         gameInfo.boardWidth = appData.NrFiles;
6034     }
6035     if(appData.NrRanks >= 0) {
6036         gameInfo.boardHeight = appData.NrRanks;
6037     }
6038     if(appData.holdingsSize >= 0) {
6039         i = appData.holdingsSize;
6040         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6041         gameInfo.holdingsSize = i;
6042     }
6043     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6044     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6045         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6046
6047     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6048     if(pawnRow < 1) pawnRow = 1;
6049     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6050
6051     /* User pieceToChar list overrules defaults */
6052     if(appData.pieceToCharTable != NULL)
6053         SetCharTable(pieceToChar, appData.pieceToCharTable);
6054
6055     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6056
6057         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6058             s = (ChessSquare) 0; /* account holding counts in guard band */
6059         for( i=0; i<BOARD_HEIGHT; i++ )
6060             initialPosition[i][j] = s;
6061
6062         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6063         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6064         initialPosition[pawnRow][j] = WhitePawn;
6065         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6066         if(gameInfo.variant == VariantXiangqi) {
6067             if(j&1) {
6068                 initialPosition[pawnRow][j] =
6069                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6070                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6071                    initialPosition[2][j] = WhiteCannon;
6072                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6073                 }
6074             }
6075         }
6076         if(gameInfo.variant == VariantGrand) {
6077             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6078                initialPosition[0][j] = WhiteRook;
6079                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6080             }
6081         }
6082         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6083     }
6084     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6085
6086             j=BOARD_LEFT+1;
6087             initialPosition[1][j] = WhiteBishop;
6088             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6089             j=BOARD_RGHT-2;
6090             initialPosition[1][j] = WhiteRook;
6091             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6092     }
6093
6094     if( nrCastlingRights == -1) {
6095         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6096         /*       This sets default castling rights from none to normal corners   */
6097         /* Variants with other castling rights must set them themselves above    */
6098         nrCastlingRights = 6;
6099
6100         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6101         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6102         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6103         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6104         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6105         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6106      }
6107
6108      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6109      if(gameInfo.variant == VariantGreat) { // promotion commoners
6110         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6111         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6112         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6113         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6114      }
6115      if( gameInfo.variant == VariantSChess ) {
6116       initialPosition[1][0] = BlackMarshall;
6117       initialPosition[2][0] = BlackAngel;
6118       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6119       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6120       initialPosition[1][1] = initialPosition[2][1] =
6121       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6122      }
6123   if (appData.debugMode) {
6124     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6125   }
6126     if(shuffleOpenings) {
6127         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6128         startedFromSetupPosition = TRUE;
6129     }
6130     if(startedFromPositionFile) {
6131       /* [HGM] loadPos: use PositionFile for every new game */
6132       CopyBoard(initialPosition, filePosition);
6133       for(i=0; i<nrCastlingRights; i++)
6134           initialRights[i] = filePosition[CASTLING][i];
6135       startedFromSetupPosition = TRUE;
6136     }
6137
6138     CopyBoard(boards[0], initialPosition);
6139
6140     if(oldx != gameInfo.boardWidth ||
6141        oldy != gameInfo.boardHeight ||
6142        oldv != gameInfo.variant ||
6143        oldh != gameInfo.holdingsWidth
6144                                          )
6145             InitDrawingSizes(-2 ,0);
6146
6147     oldv = gameInfo.variant;
6148     if (redraw)
6149       DrawPosition(TRUE, boards[currentMove]);
6150 }
6151
6152 void
6153 SendBoard (ChessProgramState *cps, int moveNum)
6154 {
6155     char message[MSG_SIZ];
6156
6157     if (cps->useSetboard) {
6158       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6159       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6160       SendToProgram(message, cps);
6161       free(fen);
6162
6163     } else {
6164       ChessSquare *bp;
6165       int i, j, left=0, right=BOARD_WIDTH;
6166       /* Kludge to set black to move, avoiding the troublesome and now
6167        * deprecated "black" command.
6168        */
6169       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6170         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6171
6172       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6173
6174       SendToProgram("edit\n", cps);
6175       SendToProgram("#\n", cps);
6176       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6177         bp = &boards[moveNum][i][left];
6178         for (j = left; j < right; j++, bp++) {
6179           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6180           if ((int) *bp < (int) BlackPawn) {
6181             if(j == BOARD_RGHT+1)
6182                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6183             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6184             if(message[0] == '+' || message[0] == '~') {
6185               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6186                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6187                         AAA + j, ONE + i);
6188             }
6189             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6190                 message[1] = BOARD_RGHT   - 1 - j + '1';
6191                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6192             }
6193             SendToProgram(message, cps);
6194           }
6195         }
6196       }
6197
6198       SendToProgram("c\n", cps);
6199       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6200         bp = &boards[moveNum][i][left];
6201         for (j = left; j < right; j++, bp++) {
6202           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6203           if (((int) *bp != (int) EmptySquare)
6204               && ((int) *bp >= (int) BlackPawn)) {
6205             if(j == BOARD_LEFT-2)
6206                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6207             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6208                     AAA + j, ONE + i);
6209             if(message[0] == '+' || message[0] == '~') {
6210               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6211                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6212                         AAA + j, ONE + i);
6213             }
6214             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6215                 message[1] = BOARD_RGHT   - 1 - j + '1';
6216                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6217             }
6218             SendToProgram(message, cps);
6219           }
6220         }
6221       }
6222
6223       SendToProgram(".\n", cps);
6224     }
6225     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6226 }
6227
6228 char exclusionHeader[MSG_SIZ];
6229 int exCnt, excludePtr;
6230 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6231 static Exclusion excluTab[200];
6232 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6233
6234 static void
6235 WriteMap (int s)
6236 {
6237     int j;
6238     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6239     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6240 }
6241
6242 static void
6243 ClearMap ()
6244 {
6245     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6246     excludePtr = 24; exCnt = 0;
6247     WriteMap(0);
6248 }
6249
6250 static void
6251 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6252 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6253     char buf[2*MOVE_LEN], *p;
6254     Exclusion *e = excluTab;
6255     int i;
6256     for(i=0; i<exCnt; i++)
6257         if(e[i].ff == fromX && e[i].fr == fromY &&
6258            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6259     if(i == exCnt) { // was not in exclude list; add it
6260         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6261         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6262             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6263             return; // abort
6264         }
6265         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6266         excludePtr++; e[i].mark = excludePtr++;
6267         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6268         exCnt++;
6269     }
6270     exclusionHeader[e[i].mark] = state;
6271 }
6272
6273 static int
6274 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6275 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6276     char buf[MSG_SIZ];
6277     int j, k;
6278     ChessMove moveType;
6279     if((signed char)promoChar == -1) { // kludge to indicate best move
6280         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6281             return 1; // if unparsable, abort
6282     }
6283     // update exclusion map (resolving toggle by consulting existing state)
6284     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6285     j = k%8; k >>= 3;
6286     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6287     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6288          excludeMap[k] |=   1<<j;
6289     else excludeMap[k] &= ~(1<<j);
6290     // update header
6291     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6292     // inform engine
6293     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6294     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6295     SendToBoth(buf);
6296     return (state == '+');
6297 }
6298
6299 static void
6300 ExcludeClick (int index)
6301 {
6302     int i, j;
6303     Exclusion *e = excluTab;
6304     if(index < 25) { // none, best or tail clicked
6305         if(index < 13) { // none: include all
6306             WriteMap(0); // clear map
6307             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6308             SendToBoth("include all\n"); // and inform engine
6309         } else if(index > 18) { // tail
6310             if(exclusionHeader[19] == '-') { // tail was excluded
6311                 SendToBoth("include all\n");
6312                 WriteMap(0); // clear map completely
6313                 // now re-exclude selected moves
6314                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6315                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6316             } else { // tail was included or in mixed state
6317                 SendToBoth("exclude all\n");
6318                 WriteMap(0xFF); // fill map completely
6319                 // now re-include selected moves
6320                 j = 0; // count them
6321                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6322                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6323                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6324             }
6325         } else { // best
6326             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6327         }
6328     } else {
6329         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6330             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6331             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6332             break;
6333         }
6334     }
6335 }
6336
6337 ChessSquare
6338 DefaultPromoChoice (int white)
6339 {
6340     ChessSquare result;
6341     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6342        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6343         result = WhiteFerz; // no choice
6344     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6345         result= WhiteKing; // in Suicide Q is the last thing we want
6346     else if(gameInfo.variant == VariantSpartan)
6347         result = white ? WhiteQueen : WhiteAngel;
6348     else result = WhiteQueen;
6349     if(!white) result = WHITE_TO_BLACK result;
6350     return result;
6351 }
6352
6353 static int autoQueen; // [HGM] oneclick
6354
6355 int
6356 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6357 {
6358     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6359     /* [HGM] add Shogi promotions */
6360     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6361     ChessSquare piece;
6362     ChessMove moveType;
6363     Boolean premove;
6364
6365     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6366     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6367
6368     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6369       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6370         return FALSE;
6371
6372     piece = boards[currentMove][fromY][fromX];
6373     if(gameInfo.variant == VariantShogi) {
6374         promotionZoneSize = BOARD_HEIGHT/3;
6375         highestPromotingPiece = (int)WhiteFerz;
6376     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6377         promotionZoneSize = 3;
6378     }
6379
6380     // Treat Lance as Pawn when it is not representing Amazon
6381     if(gameInfo.variant != VariantSuper) {
6382         if(piece == WhiteLance) piece = WhitePawn; else
6383         if(piece == BlackLance) piece = BlackPawn;
6384     }
6385
6386     // next weed out all moves that do not touch the promotion zone at all
6387     if((int)piece >= BlackPawn) {
6388         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6389              return FALSE;
6390         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6391     } else {
6392         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6393            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6394     }
6395
6396     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6397
6398     // weed out mandatory Shogi promotions
6399     if(gameInfo.variant == VariantShogi) {
6400         if(piece >= BlackPawn) {
6401             if(toY == 0 && piece == BlackPawn ||
6402                toY == 0 && piece == BlackQueen ||
6403                toY <= 1 && piece == BlackKnight) {
6404                 *promoChoice = '+';
6405                 return FALSE;
6406             }
6407         } else {
6408             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6409                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6410                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6411                 *promoChoice = '+';
6412                 return FALSE;
6413             }
6414         }
6415     }
6416
6417     // weed out obviously illegal Pawn moves
6418     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6419         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6420         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6421         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6422         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6423         // note we are not allowed to test for valid (non-)capture, due to premove
6424     }
6425
6426     // we either have a choice what to promote to, or (in Shogi) whether to promote
6427     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6428        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6429         *promoChoice = PieceToChar(BlackFerz);  // no choice
6430         return FALSE;
6431     }
6432     // no sense asking what we must promote to if it is going to explode...
6433     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6434         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6435         return FALSE;
6436     }
6437     // give caller the default choice even if we will not make it
6438     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6439     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6440     if(        sweepSelect && gameInfo.variant != VariantGreat
6441                            && gameInfo.variant != VariantGrand
6442                            && gameInfo.variant != VariantSuper) return FALSE;
6443     if(autoQueen) return FALSE; // predetermined
6444
6445     // suppress promotion popup on illegal moves that are not premoves
6446     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6447               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6448     if(appData.testLegality && !premove) {
6449         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6450                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6451         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6452             return FALSE;
6453     }
6454
6455     return TRUE;
6456 }
6457
6458 int
6459 InPalace (int row, int column)
6460 {   /* [HGM] for Xiangqi */
6461     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6462          column < (BOARD_WIDTH + 4)/2 &&
6463          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6464     return FALSE;
6465 }
6466
6467 int
6468 PieceForSquare (int x, int y)
6469 {
6470   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6471      return -1;
6472   else
6473      return boards[currentMove][y][x];
6474 }
6475
6476 int
6477 OKToStartUserMove (int x, int y)
6478 {
6479     ChessSquare from_piece;
6480     int white_piece;
6481
6482     if (matchMode) return FALSE;
6483     if (gameMode == EditPosition) return TRUE;
6484
6485     if (x >= 0 && y >= 0)
6486       from_piece = boards[currentMove][y][x];
6487     else
6488       from_piece = EmptySquare;
6489
6490     if (from_piece == EmptySquare) return FALSE;
6491
6492     white_piece = (int)from_piece >= (int)WhitePawn &&
6493       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6494
6495     switch (gameMode) {
6496       case AnalyzeFile:
6497       case TwoMachinesPlay:
6498       case EndOfGame:
6499         return FALSE;
6500
6501       case IcsObserving:
6502       case IcsIdle:
6503         return FALSE;
6504
6505       case MachinePlaysWhite:
6506       case IcsPlayingBlack:
6507         if (appData.zippyPlay) return FALSE;
6508         if (white_piece) {
6509             DisplayMoveError(_("You are playing Black"));
6510             return FALSE;
6511         }
6512         break;
6513
6514       case MachinePlaysBlack:
6515       case IcsPlayingWhite:
6516         if (appData.zippyPlay) return FALSE;
6517         if (!white_piece) {
6518             DisplayMoveError(_("You are playing White"));
6519             return FALSE;
6520         }
6521         break;
6522
6523       case PlayFromGameFile:
6524             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6525       case EditGame:
6526         if (!white_piece && WhiteOnMove(currentMove)) {
6527             DisplayMoveError(_("It is White's turn"));
6528             return FALSE;
6529         }
6530         if (white_piece && !WhiteOnMove(currentMove)) {
6531             DisplayMoveError(_("It is Black's turn"));
6532             return FALSE;
6533         }
6534         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6535             /* Editing correspondence game history */
6536             /* Could disallow this or prompt for confirmation */
6537             cmailOldMove = -1;
6538         }
6539         break;
6540
6541       case BeginningOfGame:
6542         if (appData.icsActive) return FALSE;
6543         if (!appData.noChessProgram) {
6544             if (!white_piece) {
6545                 DisplayMoveError(_("You are playing White"));
6546                 return FALSE;
6547             }
6548         }
6549         break;
6550
6551       case Training:
6552         if (!white_piece && WhiteOnMove(currentMove)) {
6553             DisplayMoveError(_("It is White's turn"));
6554             return FALSE;
6555         }
6556         if (white_piece && !WhiteOnMove(currentMove)) {
6557             DisplayMoveError(_("It is Black's turn"));
6558             return FALSE;
6559         }
6560         break;
6561
6562       default:
6563       case IcsExamining:
6564         break;
6565     }
6566     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6567         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6568         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6569         && gameMode != AnalyzeFile && gameMode != Training) {
6570         DisplayMoveError(_("Displayed position is not current"));
6571         return FALSE;
6572     }
6573     return TRUE;
6574 }
6575
6576 Boolean
6577 OnlyMove (int *x, int *y, Boolean captures)
6578 {
6579     DisambiguateClosure cl;
6580     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6581     switch(gameMode) {
6582       case MachinePlaysBlack:
6583       case IcsPlayingWhite:
6584       case BeginningOfGame:
6585         if(!WhiteOnMove(currentMove)) return FALSE;
6586         break;
6587       case MachinePlaysWhite:
6588       case IcsPlayingBlack:
6589         if(WhiteOnMove(currentMove)) return FALSE;
6590         break;
6591       case EditGame:
6592         break;
6593       default:
6594         return FALSE;
6595     }
6596     cl.pieceIn = EmptySquare;
6597     cl.rfIn = *y;
6598     cl.ffIn = *x;
6599     cl.rtIn = -1;
6600     cl.ftIn = -1;
6601     cl.promoCharIn = NULLCHAR;
6602     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6603     if( cl.kind == NormalMove ||
6604         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6605         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6606         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6607       fromX = cl.ff;
6608       fromY = cl.rf;
6609       *x = cl.ft;
6610       *y = cl.rt;
6611       return TRUE;
6612     }
6613     if(cl.kind != ImpossibleMove) return FALSE;
6614     cl.pieceIn = EmptySquare;
6615     cl.rfIn = -1;
6616     cl.ffIn = -1;
6617     cl.rtIn = *y;
6618     cl.ftIn = *x;
6619     cl.promoCharIn = NULLCHAR;
6620     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6621     if( cl.kind == NormalMove ||
6622         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6623         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6624         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6625       fromX = cl.ff;
6626       fromY = cl.rf;
6627       *x = cl.ft;
6628       *y = cl.rt;
6629       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6630       return TRUE;
6631     }
6632     return FALSE;
6633 }
6634
6635 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6636 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6637 int lastLoadGameUseList = FALSE;
6638 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6639 ChessMove lastLoadGameStart = EndOfFile;
6640 int doubleClick;
6641
6642 void
6643 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6644 {
6645     ChessMove moveType;
6646     ChessSquare pup;
6647     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6648
6649     /* Check if the user is playing in turn.  This is complicated because we
6650        let the user "pick up" a piece before it is his turn.  So the piece he
6651        tried to pick up may have been captured by the time he puts it down!
6652        Therefore we use the color the user is supposed to be playing in this
6653        test, not the color of the piece that is currently on the starting
6654        square---except in EditGame mode, where the user is playing both
6655        sides; fortunately there the capture race can't happen.  (It can
6656        now happen in IcsExamining mode, but that's just too bad.  The user
6657        will get a somewhat confusing message in that case.)
6658        */
6659
6660     switch (gameMode) {
6661       case AnalyzeFile:
6662       case TwoMachinesPlay:
6663       case EndOfGame:
6664       case IcsObserving:
6665       case IcsIdle:
6666         /* We switched into a game mode where moves are not accepted,
6667            perhaps while the mouse button was down. */
6668         return;
6669
6670       case MachinePlaysWhite:
6671         /* User is moving for Black */
6672         if (WhiteOnMove(currentMove)) {
6673             DisplayMoveError(_("It is White's turn"));
6674             return;
6675         }
6676         break;
6677
6678       case MachinePlaysBlack:
6679         /* User is moving for White */
6680         if (!WhiteOnMove(currentMove)) {
6681             DisplayMoveError(_("It is Black's turn"));
6682             return;
6683         }
6684         break;
6685
6686       case PlayFromGameFile:
6687             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6688       case EditGame:
6689       case IcsExamining:
6690       case BeginningOfGame:
6691       case AnalyzeMode:
6692       case Training:
6693         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6694         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6695             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6696             /* User is moving for Black */
6697             if (WhiteOnMove(currentMove)) {
6698                 DisplayMoveError(_("It is White's turn"));
6699                 return;
6700             }
6701         } else {
6702             /* User is moving for White */
6703             if (!WhiteOnMove(currentMove)) {
6704                 DisplayMoveError(_("It is Black's turn"));
6705                 return;
6706             }
6707         }
6708         break;
6709
6710       case IcsPlayingBlack:
6711         /* User is moving for Black */
6712         if (WhiteOnMove(currentMove)) {
6713             if (!appData.premove) {
6714                 DisplayMoveError(_("It is White's turn"));
6715             } else if (toX >= 0 && toY >= 0) {
6716                 premoveToX = toX;
6717                 premoveToY = toY;
6718                 premoveFromX = fromX;
6719                 premoveFromY = fromY;
6720                 premovePromoChar = promoChar;
6721                 gotPremove = 1;
6722                 if (appData.debugMode)
6723                     fprintf(debugFP, "Got premove: fromX %d,"
6724                             "fromY %d, toX %d, toY %d\n",
6725                             fromX, fromY, toX, toY);
6726             }
6727             return;
6728         }
6729         break;
6730
6731       case IcsPlayingWhite:
6732         /* User is moving for White */
6733         if (!WhiteOnMove(currentMove)) {
6734             if (!appData.premove) {
6735                 DisplayMoveError(_("It is Black's turn"));
6736             } else if (toX >= 0 && toY >= 0) {
6737                 premoveToX = toX;
6738                 premoveToY = toY;
6739                 premoveFromX = fromX;
6740                 premoveFromY = fromY;
6741                 premovePromoChar = promoChar;
6742                 gotPremove = 1;
6743                 if (appData.debugMode)
6744                     fprintf(debugFP, "Got premove: fromX %d,"
6745                             "fromY %d, toX %d, toY %d\n",
6746                             fromX, fromY, toX, toY);
6747             }
6748             return;
6749         }
6750         break;
6751
6752       default:
6753         break;
6754
6755       case EditPosition:
6756         /* EditPosition, empty square, or different color piece;
6757            click-click move is possible */
6758         if (toX == -2 || toY == -2) {
6759             boards[0][fromY][fromX] = EmptySquare;
6760             DrawPosition(FALSE, boards[currentMove]);
6761             return;
6762         } else if (toX >= 0 && toY >= 0) {
6763             boards[0][toY][toX] = boards[0][fromY][fromX];
6764             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6765                 if(boards[0][fromY][0] != EmptySquare) {
6766                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6767                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6768                 }
6769             } else
6770             if(fromX == BOARD_RGHT+1) {
6771                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6772                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6773                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6774                 }
6775             } else
6776             boards[0][fromY][fromX] = gatingPiece;
6777             DrawPosition(FALSE, boards[currentMove]);
6778             return;
6779         }
6780         return;
6781     }
6782
6783     if(toX < 0 || toY < 0) return;
6784     pup = boards[currentMove][toY][toX];
6785
6786     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6787     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6788          if( pup != EmptySquare ) return;
6789          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6790            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6791                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6792            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6793            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6794            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6795            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6796          fromY = DROP_RANK;
6797     }
6798
6799     /* [HGM] always test for legality, to get promotion info */
6800     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6801                                          fromY, fromX, toY, toX, promoChar);
6802
6803     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6804
6805     /* [HGM] but possibly ignore an IllegalMove result */
6806     if (appData.testLegality) {
6807         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6808             DisplayMoveError(_("Illegal move"));
6809             return;
6810         }
6811     }
6812
6813     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6814         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6815              ClearPremoveHighlights(); // was included
6816         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6817         return;
6818     }
6819
6820     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6821 }
6822
6823 /* Common tail of UserMoveEvent and DropMenuEvent */
6824 int
6825 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6826 {
6827     char *bookHit = 0;
6828
6829     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6830         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6831         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6832         if(WhiteOnMove(currentMove)) {
6833             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6834         } else {
6835             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6836         }
6837     }
6838
6839     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6840        move type in caller when we know the move is a legal promotion */
6841     if(moveType == NormalMove && promoChar)
6842         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6843
6844     /* [HGM] <popupFix> The following if has been moved here from
6845        UserMoveEvent(). Because it seemed to belong here (why not allow
6846        piece drops in training games?), and because it can only be
6847        performed after it is known to what we promote. */
6848     if (gameMode == Training) {
6849       /* compare the move played on the board to the next move in the
6850        * game. If they match, display the move and the opponent's response.
6851        * If they don't match, display an error message.
6852        */
6853       int saveAnimate;
6854       Board testBoard;
6855       CopyBoard(testBoard, boards[currentMove]);
6856       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6857
6858       if (CompareBoards(testBoard, boards[currentMove+1])) {
6859         ForwardInner(currentMove+1);
6860
6861         /* Autoplay the opponent's response.
6862          * if appData.animate was TRUE when Training mode was entered,
6863          * the response will be animated.
6864          */
6865         saveAnimate = appData.animate;
6866         appData.animate = animateTraining;
6867         ForwardInner(currentMove+1);
6868         appData.animate = saveAnimate;
6869
6870         /* check for the end of the game */
6871         if (currentMove >= forwardMostMove) {
6872           gameMode = PlayFromGameFile;
6873           ModeHighlight();
6874           SetTrainingModeOff();
6875           DisplayInformation(_("End of game"));
6876         }
6877       } else {
6878         DisplayError(_("Incorrect move"), 0);
6879       }
6880       return 1;
6881     }
6882
6883   /* Ok, now we know that the move is good, so we can kill
6884      the previous line in Analysis Mode */
6885   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6886                                 && currentMove < forwardMostMove) {
6887     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6888     else forwardMostMove = currentMove;
6889   }
6890
6891   ClearMap();
6892
6893   /* If we need the chess program but it's dead, restart it */
6894   ResurrectChessProgram();
6895
6896   /* A user move restarts a paused game*/
6897   if (pausing)
6898     PauseEvent();
6899
6900   thinkOutput[0] = NULLCHAR;
6901
6902   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6903
6904   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6905     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906     return 1;
6907   }
6908
6909   if (gameMode == BeginningOfGame) {
6910     if (appData.noChessProgram) {
6911       gameMode = EditGame;
6912       SetGameInfo();
6913     } else {
6914       char buf[MSG_SIZ];
6915       gameMode = MachinePlaysBlack;
6916       StartClocks();
6917       SetGameInfo();
6918       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6919       DisplayTitle(buf);
6920       if (first.sendName) {
6921         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6922         SendToProgram(buf, &first);
6923       }
6924       StartClocks();
6925     }
6926     ModeHighlight();
6927   }
6928
6929   /* Relay move to ICS or chess engine */
6930   if (appData.icsActive) {
6931     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6932         gameMode == IcsExamining) {
6933       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6934         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6935         SendToICS("draw ");
6936         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6937       }
6938       // also send plain move, in case ICS does not understand atomic claims
6939       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6940       ics_user_moved = 1;
6941     }
6942   } else {
6943     if (first.sendTime && (gameMode == BeginningOfGame ||
6944                            gameMode == MachinePlaysWhite ||
6945                            gameMode == MachinePlaysBlack)) {
6946       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6947     }
6948     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6949          // [HGM] book: if program might be playing, let it use book
6950         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6951         first.maybeThinking = TRUE;
6952     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6953         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6954         SendBoard(&first, currentMove+1);
6955         if(second.analyzing) {
6956             if(!second.useSetboard) SendToProgram("undo\n", &second);
6957             SendBoard(&second, currentMove+1);
6958         }
6959     } else {
6960         SendMoveToProgram(forwardMostMove-1, &first);
6961         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6962     }
6963     if (currentMove == cmailOldMove + 1) {
6964       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6965     }
6966   }
6967
6968   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6969
6970   switch (gameMode) {
6971   case EditGame:
6972     if(appData.testLegality)
6973     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6974     case MT_NONE:
6975     case MT_CHECK:
6976       break;
6977     case MT_CHECKMATE:
6978     case MT_STAINMATE:
6979       if (WhiteOnMove(currentMove)) {
6980         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6981       } else {
6982         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6983       }
6984       break;
6985     case MT_STALEMATE:
6986       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6987       break;
6988     }
6989     break;
6990
6991   case MachinePlaysBlack:
6992   case MachinePlaysWhite:
6993     /* disable certain menu options while machine is thinking */
6994     SetMachineThinkingEnables();
6995     break;
6996
6997   default:
6998     break;
6999   }
7000
7001   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7002   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7003
7004   if(bookHit) { // [HGM] book: simulate book reply
7005         static char bookMove[MSG_SIZ]; // a bit generous?
7006
7007         programStats.nodes = programStats.depth = programStats.time =
7008         programStats.score = programStats.got_only_move = 0;
7009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7010
7011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7012         strcat(bookMove, bookHit);
7013         HandleMachineMove(bookMove, &first);
7014   }
7015   return 1;
7016 }
7017
7018 void
7019 MarkByFEN(char *fen)
7020 {
7021         int r, f;
7022         if(!appData.markers || !appData.highlightDragging) return;
7023         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7024         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7025         while(*fen) {
7026             int s = 0;
7027             marker[r][f] = 0;
7028             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7029             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7030             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7031             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7032             if(*fen == 'T') marker[r][f++] = 0; else
7033             if(*fen == 'Y') marker[r][f++] = 1; else
7034             if(*fen == 'G') marker[r][f++] = 3; else
7035             if(*fen == 'B') marker[r][f++] = 4; else
7036             if(*fen == 'C') marker[r][f++] = 5; else
7037             if(*fen == 'M') marker[r][f++] = 6; else
7038             if(*fen == 'W') marker[r][f++] = 7; else
7039             if(*fen == 'D') marker[r][f++] = 8; else
7040             if(*fen == 'R') marker[r][f++] = 2; else {
7041                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7042               f += s; fen -= s>0;
7043             }
7044             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7045             if(r < 0) break;
7046             fen++;
7047         }
7048         DrawPosition(TRUE, NULL);
7049 }
7050
7051 void
7052 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7053 {
7054     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7055     Markers *m = (Markers *) closure;
7056     if(rf == fromY && ff == fromX)
7057         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7058                          || kind == WhiteCapturesEnPassant
7059                          || kind == BlackCapturesEnPassant);
7060     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7061 }
7062
7063 void
7064 MarkTargetSquares (int clear)
7065 {
7066   int x, y, sum=0;
7067   if(clear) { // no reason to ever suppress clearing
7068     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7069     if(!sum) return; // nothing was cleared,no redraw needed
7070   } else {
7071     int capt = 0;
7072     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7073        !appData.testLegality || gameMode == EditPosition) return;
7074     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7075     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7076       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7077       if(capt)
7078       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7079     }
7080   }
7081   DrawPosition(FALSE, NULL);
7082 }
7083
7084 int
7085 Explode (Board board, int fromX, int fromY, int toX, int toY)
7086 {
7087     if(gameInfo.variant == VariantAtomic &&
7088        (board[toY][toX] != EmptySquare ||                     // capture?
7089         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7090                          board[fromY][fromX] == BlackPawn   )
7091       )) {
7092         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7093         return TRUE;
7094     }
7095     return FALSE;
7096 }
7097
7098 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7099
7100 int
7101 CanPromote (ChessSquare piece, int y)
7102 {
7103         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7104         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7105         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7106            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7107            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7108          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7109         return (piece == BlackPawn && y == 1 ||
7110                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7111                 piece == BlackLance && y == 1 ||
7112                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7113 }
7114
7115 void
7116 HoverEvent (int hiX, int hiY, int x, int y)
7117 {
7118         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7119         int r, f;
7120         if(!first.highlight) return;
7121         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7122           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7123             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7124         else if(hiX != x || hiY != y) {
7125           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7126           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7127             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7128           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7129             char buf[MSG_SIZ];
7130             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7131             SendToProgram(buf, &first);
7132           }
7133           SetHighlights(fromX, fromY, x, y);
7134         }
7135 }
7136
7137 void ReportClick(char *action, int x, int y)
7138 {
7139         char buf[MSG_SIZ]; // Inform engine of what user does
7140         int r, f;
7141         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7142           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7143         if(!first.highlight || gameMode == EditPosition) return;
7144         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7145         SendToProgram(buf, &first);
7146 }
7147
7148 void
7149 LeftClick (ClickType clickType, int xPix, int yPix)
7150 {
7151     int x, y;
7152     Boolean saveAnimate;
7153     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7154     char promoChoice = NULLCHAR;
7155     ChessSquare piece;
7156     static TimeMark lastClickTime, prevClickTime;
7157
7158     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7159
7160     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7161
7162     if (clickType == Press) ErrorPopDown();
7163
7164     x = EventToSquare(xPix, BOARD_WIDTH);
7165     y = EventToSquare(yPix, BOARD_HEIGHT);
7166     if (!flipView && y >= 0) {
7167         y = BOARD_HEIGHT - 1 - y;
7168     }
7169     if (flipView && x >= 0) {
7170         x = BOARD_WIDTH - 1 - x;
7171     }
7172
7173     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7174         defaultPromoChoice = promoSweep;
7175         promoSweep = EmptySquare;   // terminate sweep
7176         promoDefaultAltered = TRUE;
7177         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7178     }
7179
7180     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7181         if(clickType == Release) return; // ignore upclick of click-click destination
7182         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7183         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7184         if(gameInfo.holdingsWidth &&
7185                 (WhiteOnMove(currentMove)
7186                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7187                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7188             // click in right holdings, for determining promotion piece
7189             ChessSquare p = boards[currentMove][y][x];
7190             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7191             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7192             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7193                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7194                 fromX = fromY = -1;
7195                 return;
7196             }
7197         }
7198         DrawPosition(FALSE, boards[currentMove]);
7199         return;
7200     }
7201
7202     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7203     if(clickType == Press
7204             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7205               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7206               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7207         return;
7208
7209     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7210         // could be static click on premove from-square: abort premove
7211         gotPremove = 0;
7212         ClearPremoveHighlights();
7213     }
7214
7215     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7216         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7217
7218     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7219         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7220                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7221         defaultPromoChoice = DefaultPromoChoice(side);
7222     }
7223
7224     autoQueen = appData.alwaysPromoteToQueen;
7225
7226     if (fromX == -1) {
7227       int originalY = y;
7228       gatingPiece = EmptySquare;
7229       if (clickType != Press) {
7230         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7231             DragPieceEnd(xPix, yPix); dragging = 0;
7232             DrawPosition(FALSE, NULL);
7233         }
7234         return;
7235       }
7236       doubleClick = FALSE;
7237       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7238         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7239       }
7240       fromX = x; fromY = y; toX = toY = -1;
7241       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7242          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7243          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7244             /* First square */
7245             if (OKToStartUserMove(fromX, fromY)) {
7246                 second = 0;
7247                 ReportClick("lift", x, y);
7248                 MarkTargetSquares(0);
7249                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7250                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7251                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7252                     promoSweep = defaultPromoChoice;
7253                     selectFlag = 0; lastX = xPix; lastY = yPix;
7254                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7255                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7256                 }
7257                 if (appData.highlightDragging) {
7258                     SetHighlights(fromX, fromY, -1, -1);
7259                 } else {
7260                     ClearHighlights();
7261                 }
7262             } else fromX = fromY = -1;
7263             return;
7264         }
7265     }
7266
7267     /* fromX != -1 */
7268     if (clickType == Press && gameMode != EditPosition) {
7269         ChessSquare fromP;
7270         ChessSquare toP;
7271         int frc;
7272
7273         // ignore off-board to clicks
7274         if(y < 0 || x < 0) return;
7275
7276         /* Check if clicking again on the same color piece */
7277         fromP = boards[currentMove][fromY][fromX];
7278         toP = boards[currentMove][y][x];
7279         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7280         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7281              WhitePawn <= toP && toP <= WhiteKing &&
7282              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7283              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7284             (BlackPawn <= fromP && fromP <= BlackKing &&
7285              BlackPawn <= toP && toP <= BlackKing &&
7286              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7287              !(fromP == BlackKing && toP == BlackRook && frc))) {
7288             /* Clicked again on same color piece -- changed his mind */
7289             second = (x == fromX && y == fromY);
7290             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7291                 second = FALSE; // first double-click rather than scond click
7292                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7293             }
7294             promoDefaultAltered = FALSE;
7295             MarkTargetSquares(1);
7296            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7297             if (appData.highlightDragging) {
7298                 SetHighlights(x, y, -1, -1);
7299             } else {
7300                 ClearHighlights();
7301             }
7302             if (OKToStartUserMove(x, y)) {
7303                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7304                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7305                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7306                  gatingPiece = boards[currentMove][fromY][fromX];
7307                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7308                 fromX = x;
7309                 fromY = y; dragging = 1;
7310                 ReportClick("lift", x, y);
7311                 MarkTargetSquares(0);
7312                 DragPieceBegin(xPix, yPix, FALSE);
7313                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7314                     promoSweep = defaultPromoChoice;
7315                     selectFlag = 0; lastX = xPix; lastY = yPix;
7316                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7317                 }
7318             }
7319            }
7320            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7321            second = FALSE;
7322         }
7323         // ignore clicks on holdings
7324         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7325     }
7326
7327     if (clickType == Release && x == fromX && y == fromY) {
7328         DragPieceEnd(xPix, yPix); dragging = 0;
7329         if(clearFlag) {
7330             // a deferred attempt to click-click move an empty square on top of a piece
7331             boards[currentMove][y][x] = EmptySquare;
7332             ClearHighlights();
7333             DrawPosition(FALSE, boards[currentMove]);
7334             fromX = fromY = -1; clearFlag = 0;
7335             return;
7336         }
7337         if (appData.animateDragging) {
7338             /* Undo animation damage if any */
7339             DrawPosition(FALSE, NULL);
7340         }
7341         if (second || sweepSelecting) {
7342             /* Second up/down in same square; just abort move */
7343             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7344             second = sweepSelecting = 0;
7345             fromX = fromY = -1;
7346             gatingPiece = EmptySquare;
7347             MarkTargetSquares(1);
7348             ClearHighlights();
7349             gotPremove = 0;
7350             ClearPremoveHighlights();
7351         } else {
7352             /* First upclick in same square; start click-click mode */
7353             SetHighlights(x, y, -1, -1);
7354         }
7355         return;
7356     }
7357
7358     clearFlag = 0;
7359
7360     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7361         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7362         DisplayMessage(_("only marked squares are legal"),"");
7363         DrawPosition(TRUE, NULL);
7364         return; // ignore to-click
7365     }
7366
7367     /* we now have a different from- and (possibly off-board) to-square */
7368     /* Completed move */
7369     if(!sweepSelecting) {
7370         toX = x;
7371         toY = y;
7372     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7373
7374     saveAnimate = appData.animate;
7375     if (clickType == Press) {
7376         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7377             // must be Edit Position mode with empty-square selected
7378             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7379             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7380             return;
7381         }
7382         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7383           if(appData.sweepSelect) {
7384             ChessSquare piece = boards[currentMove][fromY][fromX];
7385             promoSweep = defaultPromoChoice;
7386             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7387             selectFlag = 0; lastX = xPix; lastY = yPix;
7388             Sweep(0); // Pawn that is going to promote: preview promotion piece
7389             sweepSelecting = 1;
7390             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7391             MarkTargetSquares(1);
7392           }
7393           return; // promo popup appears on up-click
7394         }
7395         /* Finish clickclick move */
7396         if (appData.animate || appData.highlightLastMove) {
7397             SetHighlights(fromX, fromY, toX, toY);
7398         } else {
7399             ClearHighlights();
7400         }
7401     } else {
7402 #if 0
7403 // [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
7404         /* Finish drag move */
7405         if (appData.highlightLastMove) {
7406             SetHighlights(fromX, fromY, toX, toY);
7407         } else {
7408             ClearHighlights();
7409         }
7410 #endif
7411         DragPieceEnd(xPix, yPix); dragging = 0;
7412         /* Don't animate move and drag both */
7413         appData.animate = FALSE;
7414     }
7415
7416     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7417     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7418         ChessSquare piece = boards[currentMove][fromY][fromX];
7419         if(gameMode == EditPosition && piece != EmptySquare &&
7420            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7421             int n;
7422
7423             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7424                 n = PieceToNumber(piece - (int)BlackPawn);
7425                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7426                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7427                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7428             } else
7429             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7430                 n = PieceToNumber(piece);
7431                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7432                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7433                 boards[currentMove][n][BOARD_WIDTH-2]++;
7434             }
7435             boards[currentMove][fromY][fromX] = EmptySquare;
7436         }
7437         ClearHighlights();
7438         fromX = fromY = -1;
7439         MarkTargetSquares(1);
7440         DrawPosition(TRUE, boards[currentMove]);
7441         return;
7442     }
7443
7444     // off-board moves should not be highlighted
7445     if(x < 0 || y < 0) ClearHighlights();
7446     else ReportClick("put", x, y);
7447
7448     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7449
7450     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7451         SetHighlights(fromX, fromY, toX, toY);
7452         MarkTargetSquares(1);
7453         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7454             // [HGM] super: promotion to captured piece selected from holdings
7455             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7456             promotionChoice = TRUE;
7457             // kludge follows to temporarily execute move on display, without promoting yet
7458             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7459             boards[currentMove][toY][toX] = p;
7460             DrawPosition(FALSE, boards[currentMove]);
7461             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7462             boards[currentMove][toY][toX] = q;
7463             DisplayMessage("Click in holdings to choose piece", "");
7464             return;
7465         }
7466         PromotionPopUp();
7467     } else {
7468         int oldMove = currentMove;
7469         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7470         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7471         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7472         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7473            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7474             DrawPosition(TRUE, boards[currentMove]);
7475         MarkTargetSquares(1);
7476         fromX = fromY = -1;
7477     }
7478     appData.animate = saveAnimate;
7479     if (appData.animate || appData.animateDragging) {
7480         /* Undo animation damage if needed */
7481         DrawPosition(FALSE, NULL);
7482     }
7483 }
7484
7485 int
7486 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7487 {   // front-end-free part taken out of PieceMenuPopup
7488     int whichMenu; int xSqr, ySqr;
7489
7490     if(seekGraphUp) { // [HGM] seekgraph
7491         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7492         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7493         return -2;
7494     }
7495
7496     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7497          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7498         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7499         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7500         if(action == Press)   {
7501             originalFlip = flipView;
7502             flipView = !flipView; // temporarily flip board to see game from partners perspective
7503             DrawPosition(TRUE, partnerBoard);
7504             DisplayMessage(partnerStatus, "");
7505             partnerUp = TRUE;
7506         } else if(action == Release) {
7507             flipView = originalFlip;
7508             DrawPosition(TRUE, boards[currentMove]);
7509             partnerUp = FALSE;
7510         }
7511         return -2;
7512     }
7513
7514     xSqr = EventToSquare(x, BOARD_WIDTH);
7515     ySqr = EventToSquare(y, BOARD_HEIGHT);
7516     if (action == Release) {
7517         if(pieceSweep != EmptySquare) {
7518             EditPositionMenuEvent(pieceSweep, toX, toY);
7519             pieceSweep = EmptySquare;
7520         } else UnLoadPV(); // [HGM] pv
7521     }
7522     if (action != Press) return -2; // return code to be ignored
7523     switch (gameMode) {
7524       case IcsExamining:
7525         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7526       case EditPosition:
7527         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7528         if (xSqr < 0 || ySqr < 0) return -1;
7529         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7530         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7531         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7532         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7533         NextPiece(0);
7534         return 2; // grab
7535       case IcsObserving:
7536         if(!appData.icsEngineAnalyze) return -1;
7537       case IcsPlayingWhite:
7538       case IcsPlayingBlack:
7539         if(!appData.zippyPlay) goto noZip;
7540       case AnalyzeMode:
7541       case AnalyzeFile:
7542       case MachinePlaysWhite:
7543       case MachinePlaysBlack:
7544       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7545         if (!appData.dropMenu) {
7546           LoadPV(x, y);
7547           return 2; // flag front-end to grab mouse events
7548         }
7549         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7550            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7551       case EditGame:
7552       noZip:
7553         if (xSqr < 0 || ySqr < 0) return -1;
7554         if (!appData.dropMenu || appData.testLegality &&
7555             gameInfo.variant != VariantBughouse &&
7556             gameInfo.variant != VariantCrazyhouse) return -1;
7557         whichMenu = 1; // drop menu
7558         break;
7559       default:
7560         return -1;
7561     }
7562
7563     if (((*fromX = xSqr) < 0) ||
7564         ((*fromY = ySqr) < 0)) {
7565         *fromX = *fromY = -1;
7566         return -1;
7567     }
7568     if (flipView)
7569       *fromX = BOARD_WIDTH - 1 - *fromX;
7570     else
7571       *fromY = BOARD_HEIGHT - 1 - *fromY;
7572
7573     return whichMenu;
7574 }
7575
7576 void
7577 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7578 {
7579 //    char * hint = lastHint;
7580     FrontEndProgramStats stats;
7581
7582     stats.which = cps == &first ? 0 : 1;
7583     stats.depth = cpstats->depth;
7584     stats.nodes = cpstats->nodes;
7585     stats.score = cpstats->score;
7586     stats.time = cpstats->time;
7587     stats.pv = cpstats->movelist;
7588     stats.hint = lastHint;
7589     stats.an_move_index = 0;
7590     stats.an_move_count = 0;
7591
7592     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7593         stats.hint = cpstats->move_name;
7594         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7595         stats.an_move_count = cpstats->nr_moves;
7596     }
7597
7598     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
7599
7600     SetProgramStats( &stats );
7601 }
7602
7603 void
7604 ClearEngineOutputPane (int which)
7605 {
7606     static FrontEndProgramStats dummyStats;
7607     dummyStats.which = which;
7608     dummyStats.pv = "#";
7609     SetProgramStats( &dummyStats );
7610 }
7611
7612 #define MAXPLAYERS 500
7613
7614 char *
7615 TourneyStandings (int display)
7616 {
7617     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7618     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7619     char result, *p, *names[MAXPLAYERS];
7620
7621     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7622         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7623     names[0] = p = strdup(appData.participants);
7624     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7625
7626     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7627
7628     while(result = appData.results[nr]) {
7629         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7630         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7631         wScore = bScore = 0;
7632         switch(result) {
7633           case '+': wScore = 2; break;
7634           case '-': bScore = 2; break;
7635           case '=': wScore = bScore = 1; break;
7636           case ' ':
7637           case '*': return strdup("busy"); // tourney not finished
7638         }
7639         score[w] += wScore;
7640         score[b] += bScore;
7641         games[w]++;
7642         games[b]++;
7643         nr++;
7644     }
7645     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7646     for(w=0; w<nPlayers; w++) {
7647         bScore = -1;
7648         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7649         ranking[w] = b; points[w] = bScore; score[b] = -2;
7650     }
7651     p = malloc(nPlayers*34+1);
7652     for(w=0; w<nPlayers && w<display; w++)
7653         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7654     free(names[0]);
7655     return p;
7656 }
7657
7658 void
7659 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7660 {       // count all piece types
7661         int p, f, r;
7662         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7663         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7664         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7665                 p = board[r][f];
7666                 pCnt[p]++;
7667                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7668                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7669                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7670                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7671                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7672                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7673         }
7674 }
7675
7676 int
7677 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7678 {
7679         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7680         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7681
7682         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7683         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7684         if(myPawns == 2 && nMine == 3) // KPP
7685             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7686         if(myPawns == 1 && nMine == 2) // KP
7687             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7688         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7689             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7690         if(myPawns) return FALSE;
7691         if(pCnt[WhiteRook+side])
7692             return pCnt[BlackRook-side] ||
7693                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7694                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7695                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7696         if(pCnt[WhiteCannon+side]) {
7697             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7698             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7699         }
7700         if(pCnt[WhiteKnight+side])
7701             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7702         return FALSE;
7703 }
7704
7705 int
7706 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7707 {
7708         VariantClass v = gameInfo.variant;
7709
7710         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7711         if(v == VariantShatranj) return TRUE; // always winnable through baring
7712         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7713         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7714
7715         if(v == VariantXiangqi) {
7716                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7717
7718                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7719                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7720                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7721                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7722                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7723                 if(stale) // we have at least one last-rank P plus perhaps C
7724                     return majors // KPKX
7725                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7726                 else // KCA*E*
7727                     return pCnt[WhiteFerz+side] // KCAK
7728                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7729                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7730                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7731
7732         } else if(v == VariantKnightmate) {
7733                 if(nMine == 1) return FALSE;
7734                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7735         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7736                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7737
7738                 if(nMine == 1) return FALSE; // bare King
7739                 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
7740                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7741                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7742                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7743                 if(pCnt[WhiteKnight+side])
7744                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7745                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7746                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7747                 if(nBishops)
7748                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7749                 if(pCnt[WhiteAlfil+side])
7750                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7751                 if(pCnt[WhiteWazir+side])
7752                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7753         }
7754
7755         return TRUE;
7756 }
7757
7758 int
7759 CompareWithRights (Board b1, Board b2)
7760 {
7761     int rights = 0;
7762     if(!CompareBoards(b1, b2)) return FALSE;
7763     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7764     /* compare castling rights */
7765     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7766            rights++; /* King lost rights, while rook still had them */
7767     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7768         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7769            rights++; /* but at least one rook lost them */
7770     }
7771     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7772            rights++;
7773     if( b1[CASTLING][5] != NoRights ) {
7774         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7775            rights++;
7776     }
7777     return rights == 0;
7778 }
7779
7780 int
7781 Adjudicate (ChessProgramState *cps)
7782 {       // [HGM] some adjudications useful with buggy engines
7783         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7784         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7785         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7786         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7787         int k, drop, count = 0; static int bare = 1;
7788         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7789         Boolean canAdjudicate = !appData.icsActive;
7790
7791         // most tests only when we understand the game, i.e. legality-checking on
7792             if( appData.testLegality )
7793             {   /* [HGM] Some more adjudications for obstinate engines */
7794                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7795                 static int moveCount = 6;
7796                 ChessMove result;
7797                 char *reason = NULL;
7798
7799                 /* Count what is on board. */
7800                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7801
7802                 /* Some material-based adjudications that have to be made before stalemate test */
7803                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7804                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7805                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7806                      if(canAdjudicate && appData.checkMates) {
7807                          if(engineOpponent)
7808                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7809                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7810                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7811                          return 1;
7812                      }
7813                 }
7814
7815                 /* Bare King in Shatranj (loses) or Losers (wins) */
7816                 if( nrW == 1 || nrB == 1) {
7817                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7818                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7819                      if(canAdjudicate && appData.checkMates) {
7820                          if(engineOpponent)
7821                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7822                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7823                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7824                          return 1;
7825                      }
7826                   } else
7827                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7828                   {    /* bare King */
7829                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7830                         if(canAdjudicate && appData.checkMates) {
7831                             /* but only adjudicate if adjudication enabled */
7832                             if(engineOpponent)
7833                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7834                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7835                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7836                             return 1;
7837                         }
7838                   }
7839                 } else bare = 1;
7840
7841
7842             // don't wait for engine to announce game end if we can judge ourselves
7843             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7844               case MT_CHECK:
7845                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7846                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7847                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7848                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7849                             checkCnt++;
7850                         if(checkCnt >= 2) {
7851                             reason = "Xboard adjudication: 3rd check";
7852                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7853                             break;
7854                         }
7855                     }
7856                 }
7857               case MT_NONE:
7858               default:
7859                 break;
7860               case MT_STALEMATE:
7861               case MT_STAINMATE:
7862                 reason = "Xboard adjudication: Stalemate";
7863                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7864                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7865                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7866                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7867                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7868                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7869                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7870                                                                         EP_CHECKMATE : EP_WINS);
7871                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7872                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7873                 }
7874                 break;
7875               case MT_CHECKMATE:
7876                 reason = "Xboard adjudication: Checkmate";
7877                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7878                 if(gameInfo.variant == VariantShogi) {
7879                     if(forwardMostMove > backwardMostMove
7880                        && moveList[forwardMostMove-1][1] == '@'
7881                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7882                         reason = "XBoard adjudication: pawn-drop mate";
7883                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7884                     }
7885                 }
7886                 break;
7887             }
7888
7889                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7890                     case EP_STALEMATE:
7891                         result = GameIsDrawn; break;
7892                     case EP_CHECKMATE:
7893                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7894                     case EP_WINS:
7895                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7896                     default:
7897                         result = EndOfFile;
7898                 }
7899                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7900                     if(engineOpponent)
7901                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7902                     GameEnds( result, reason, GE_XBOARD );
7903                     return 1;
7904                 }
7905
7906                 /* Next absolutely insufficient mating material. */
7907                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7908                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7909                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7910
7911                      /* always flag draws, for judging claims */
7912                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7913
7914                      if(canAdjudicate && appData.materialDraws) {
7915                          /* but only adjudicate them if adjudication enabled */
7916                          if(engineOpponent) {
7917                            SendToProgram("force\n", engineOpponent); // suppress reply
7918                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7919                          }
7920                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7921                          return 1;
7922                      }
7923                 }
7924
7925                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7926                 if(gameInfo.variant == VariantXiangqi ?
7927                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7928                  : nrW + nrB == 4 &&
7929                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7930                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7931                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7932                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7933                    ) ) {
7934                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7935                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7936                           if(engineOpponent) {
7937                             SendToProgram("force\n", engineOpponent); // suppress reply
7938                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7939                           }
7940                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7941                           return 1;
7942                      }
7943                 } else moveCount = 6;
7944             }
7945
7946         // Repetition draws and 50-move rule can be applied independently of legality testing
7947
7948                 /* Check for rep-draws */
7949                 count = 0;
7950                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7951                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7952                 for(k = forwardMostMove-2;
7953                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7954                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7955                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7956                     k-=2)
7957                 {   int rights=0;
7958                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7959                         /* compare castling rights */
7960                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7961                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7962                                 rights++; /* King lost rights, while rook still had them */
7963                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7964                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7965                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7966                                    rights++; /* but at least one rook lost them */
7967                         }
7968                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7969                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7970                                 rights++;
7971                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7972                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7973                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7974                                    rights++;
7975                         }
7976                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7977                             && appData.drawRepeats > 1) {
7978                              /* adjudicate after user-specified nr of repeats */
7979                              int result = GameIsDrawn;
7980                              char *details = "XBoard adjudication: repetition draw";
7981                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7982                                 // [HGM] xiangqi: check for forbidden perpetuals
7983                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7984                                 for(m=forwardMostMove; m>k; m-=2) {
7985                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7986                                         ourPerpetual = 0; // the current mover did not always check
7987                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7988                                         hisPerpetual = 0; // the opponent did not always check
7989                                 }
7990                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7991                                                                         ourPerpetual, hisPerpetual);
7992                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7993                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7994                                     details = "Xboard adjudication: perpetual checking";
7995                                 } else
7996                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7997                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7998                                 } else
7999                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8000                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8001                                         result = BlackWins;
8002                                         details = "Xboard adjudication: repetition";
8003                                     }
8004                                 } else // it must be XQ
8005                                 // Now check for perpetual chases
8006                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8007                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8008                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8009                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8010                                         static char resdet[MSG_SIZ];
8011                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8012                                         details = resdet;
8013                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8014                                     } else
8015                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8016                                         break; // Abort repetition-checking loop.
8017                                 }
8018                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8019                              }
8020                              if(engineOpponent) {
8021                                SendToProgram("force\n", engineOpponent); // suppress reply
8022                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8023                              }
8024                              GameEnds( result, details, GE_XBOARD );
8025                              return 1;
8026                         }
8027                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8028                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8029                     }
8030                 }
8031
8032                 /* Now we test for 50-move draws. Determine ply count */
8033                 count = forwardMostMove;
8034                 /* look for last irreversble move */
8035                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8036                     count--;
8037                 /* if we hit starting position, add initial plies */
8038                 if( count == backwardMostMove )
8039                     count -= initialRulePlies;
8040                 count = forwardMostMove - count;
8041                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8042                         // adjust reversible move counter for checks in Xiangqi
8043                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8044                         if(i < backwardMostMove) i = backwardMostMove;
8045                         while(i <= forwardMostMove) {
8046                                 lastCheck = inCheck; // check evasion does not count
8047                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8048                                 if(inCheck || lastCheck) count--; // check does not count
8049                                 i++;
8050                         }
8051                 }
8052                 if( count >= 100)
8053                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8054                          /* this is used to judge if draw claims are legal */
8055                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8056                          if(engineOpponent) {
8057                            SendToProgram("force\n", engineOpponent); // suppress reply
8058                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8059                          }
8060                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8061                          return 1;
8062                 }
8063
8064                 /* if draw offer is pending, treat it as a draw claim
8065                  * when draw condition present, to allow engines a way to
8066                  * claim draws before making their move to avoid a race
8067                  * condition occurring after their move
8068                  */
8069                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8070                          char *p = NULL;
8071                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8072                              p = "Draw claim: 50-move rule";
8073                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8074                              p = "Draw claim: 3-fold repetition";
8075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8076                              p = "Draw claim: insufficient mating material";
8077                          if( p != NULL && canAdjudicate) {
8078                              if(engineOpponent) {
8079                                SendToProgram("force\n", engineOpponent); // suppress reply
8080                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8081                              }
8082                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8083                              return 1;
8084                          }
8085                 }
8086
8087                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8088                     if(engineOpponent) {
8089                       SendToProgram("force\n", engineOpponent); // suppress reply
8090                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8091                     }
8092                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8093                     return 1;
8094                 }
8095         return 0;
8096 }
8097
8098 char *
8099 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8100 {   // [HGM] book: this routine intercepts moves to simulate book replies
8101     char *bookHit = NULL;
8102
8103     //first determine if the incoming move brings opponent into his book
8104     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8105         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8106     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8107     if(bookHit != NULL && !cps->bookSuspend) {
8108         // make sure opponent is not going to reply after receiving move to book position
8109         SendToProgram("force\n", cps);
8110         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8111     }
8112     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8113     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8114     // now arrange restart after book miss
8115     if(bookHit) {
8116         // after a book hit we never send 'go', and the code after the call to this routine
8117         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8118         char buf[MSG_SIZ], *move = bookHit;
8119         if(cps->useSAN) {
8120             int fromX, fromY, toX, toY;
8121             char promoChar;
8122             ChessMove moveType;
8123             move = buf + 30;
8124             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8125                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8126                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8127                                     PosFlags(forwardMostMove),
8128                                     fromY, fromX, toY, toX, promoChar, move);
8129             } else {
8130                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8131                 bookHit = NULL;
8132             }
8133         }
8134         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8135         SendToProgram(buf, cps);
8136         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8137     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8138         SendToProgram("go\n", cps);
8139         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8140     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8141         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8142             SendToProgram("go\n", cps);
8143         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8144     }
8145     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8146 }
8147
8148 int
8149 LoadError (char *errmess, ChessProgramState *cps)
8150 {   // unloads engine and switches back to -ncp mode if it was first
8151     if(cps->initDone) return FALSE;
8152     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8153     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8154     cps->pr = NoProc;
8155     if(cps == &first) {
8156         appData.noChessProgram = TRUE;
8157         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8158         gameMode = BeginningOfGame; ModeHighlight();
8159         SetNCPMode();
8160     }
8161     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8162     DisplayMessage("", ""); // erase waiting message
8163     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8164     return TRUE;
8165 }
8166
8167 char *savedMessage;
8168 ChessProgramState *savedState;
8169 void
8170 DeferredBookMove (void)
8171 {
8172         if(savedState->lastPing != savedState->lastPong)
8173                     ScheduleDelayedEvent(DeferredBookMove, 10);
8174         else
8175         HandleMachineMove(savedMessage, savedState);
8176 }
8177
8178 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8179 static ChessProgramState *stalledEngine;
8180 static char stashedInputMove[MSG_SIZ];
8181
8182 void
8183 HandleMachineMove (char *message, ChessProgramState *cps)
8184 {
8185     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8186     char realname[MSG_SIZ];
8187     int fromX, fromY, toX, toY;
8188     ChessMove moveType;
8189     char promoChar;
8190     char *p, *pv=buf1;
8191     int machineWhite, oldError;
8192     char *bookHit;
8193
8194     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8195         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8196         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8197             DisplayError(_("Invalid pairing from pairing engine"), 0);
8198             return;
8199         }
8200         pairingReceived = 1;
8201         NextMatchGame();
8202         return; // Skim the pairing messages here.
8203     }
8204
8205     oldError = cps->userError; cps->userError = 0;
8206
8207 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8208     /*
8209      * Kludge to ignore BEL characters
8210      */
8211     while (*message == '\007') message++;
8212
8213     /*
8214      * [HGM] engine debug message: ignore lines starting with '#' character
8215      */
8216     if(cps->debug && *message == '#') return;
8217
8218     /*
8219      * Look for book output
8220      */
8221     if (cps == &first && bookRequested) {
8222         if (message[0] == '\t' || message[0] == ' ') {
8223             /* Part of the book output is here; append it */
8224             strcat(bookOutput, message);
8225             strcat(bookOutput, "  \n");
8226             return;
8227         } else if (bookOutput[0] != NULLCHAR) {
8228             /* All of book output has arrived; display it */
8229             char *p = bookOutput;
8230             while (*p != NULLCHAR) {
8231                 if (*p == '\t') *p = ' ';
8232                 p++;
8233             }
8234             DisplayInformation(bookOutput);
8235             bookRequested = FALSE;
8236             /* Fall through to parse the current output */
8237         }
8238     }
8239
8240     /*
8241      * Look for machine move.
8242      */
8243     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8244         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8245     {
8246         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8247             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8248             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8249             stalledEngine = cps;
8250             if(appData.ponderNextMove) { // bring opponent out of ponder
8251                 if(gameMode == TwoMachinesPlay) {
8252                     if(cps->other->pause)
8253                         PauseEngine(cps->other);
8254                     else
8255                         SendToProgram("easy\n", cps->other);
8256                 }
8257             }
8258             StopClocks();
8259             return;
8260         }
8261
8262         /* This method is only useful on engines that support ping */
8263         if (cps->lastPing != cps->lastPong) {
8264           if (gameMode == BeginningOfGame) {
8265             /* Extra move from before last new; ignore */
8266             if (appData.debugMode) {
8267                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8268             }
8269           } else {
8270             if (appData.debugMode) {
8271                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8272                         cps->which, gameMode);
8273             }
8274
8275             SendToProgram("undo\n", cps);
8276           }
8277           return;
8278         }
8279
8280         switch (gameMode) {
8281           case BeginningOfGame:
8282             /* Extra move from before last reset; ignore */
8283             if (appData.debugMode) {
8284                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8285             }
8286             return;
8287
8288           case EndOfGame:
8289           case IcsIdle:
8290           default:
8291             /* Extra move after we tried to stop.  The mode test is
8292                not a reliable way of detecting this problem, but it's
8293                the best we can do on engines that don't support ping.
8294             */
8295             if (appData.debugMode) {
8296                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8297                         cps->which, gameMode);
8298             }
8299             SendToProgram("undo\n", cps);
8300             return;
8301
8302           case MachinePlaysWhite:
8303           case IcsPlayingWhite:
8304             machineWhite = TRUE;
8305             break;
8306
8307           case MachinePlaysBlack:
8308           case IcsPlayingBlack:
8309             machineWhite = FALSE;
8310             break;
8311
8312           case TwoMachinesPlay:
8313             machineWhite = (cps->twoMachinesColor[0] == 'w');
8314             break;
8315         }
8316         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8317             if (appData.debugMode) {
8318                 fprintf(debugFP,
8319                         "Ignoring move out of turn by %s, gameMode %d"
8320                         ", forwardMost %d\n",
8321                         cps->which, gameMode, forwardMostMove);
8322             }
8323             return;
8324         }
8325
8326         if(cps->alphaRank) AlphaRank(machineMove, 4);
8327         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8328                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8329             /* Machine move could not be parsed; ignore it. */
8330           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8331                     machineMove, _(cps->which));
8332             DisplayMoveError(buf1);
8333             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8334                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8335             if (gameMode == TwoMachinesPlay) {
8336               GameEnds(machineWhite ? BlackWins : WhiteWins,
8337                        buf1, GE_XBOARD);
8338             }
8339             return;
8340         }
8341
8342         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8343         /* So we have to redo legality test with true e.p. status here,  */
8344         /* to make sure an illegal e.p. capture does not slip through,   */
8345         /* to cause a forfeit on a justified illegal-move complaint      */
8346         /* of the opponent.                                              */
8347         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8348            ChessMove moveType;
8349            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8350                              fromY, fromX, toY, toX, promoChar);
8351             if(moveType == IllegalMove) {
8352               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8353                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8354                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8355                            buf1, GE_XBOARD);
8356                 return;
8357            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8358            /* [HGM] Kludge to handle engines that send FRC-style castling
8359               when they shouldn't (like TSCP-Gothic) */
8360            switch(moveType) {
8361              case WhiteASideCastleFR:
8362              case BlackASideCastleFR:
8363                toX+=2;
8364                currentMoveString[2]++;
8365                break;
8366              case WhiteHSideCastleFR:
8367              case BlackHSideCastleFR:
8368                toX--;
8369                currentMoveString[2]--;
8370                break;
8371              default: ; // nothing to do, but suppresses warning of pedantic compilers
8372            }
8373         }
8374         hintRequested = FALSE;
8375         lastHint[0] = NULLCHAR;
8376         bookRequested = FALSE;
8377         /* Program may be pondering now */
8378         cps->maybeThinking = TRUE;
8379         if (cps->sendTime == 2) cps->sendTime = 1;
8380         if (cps->offeredDraw) cps->offeredDraw--;
8381
8382         /* [AS] Save move info*/
8383         pvInfoList[ forwardMostMove ].score = programStats.score;
8384         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8385         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8386
8387         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8388
8389         /* Test suites abort the 'game' after one move */
8390         if(*appData.finger) {
8391            static FILE *f;
8392            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8393            if(!f) f = fopen(appData.finger, "w");
8394            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8395            else { DisplayFatalError("Bad output file", errno, 0); return; }
8396            free(fen);
8397            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8398         }
8399
8400         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8401         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8402             int count = 0;
8403
8404             while( count < adjudicateLossPlies ) {
8405                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8406
8407                 if( count & 1 ) {
8408                     score = -score; /* Flip score for winning side */
8409                 }
8410
8411                 if( score > adjudicateLossThreshold ) {
8412                     break;
8413                 }
8414
8415                 count++;
8416             }
8417
8418             if( count >= adjudicateLossPlies ) {
8419                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8420
8421                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8422                     "Xboard adjudication",
8423                     GE_XBOARD );
8424
8425                 return;
8426             }
8427         }
8428
8429         if(Adjudicate(cps)) {
8430             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8431             return; // [HGM] adjudicate: for all automatic game ends
8432         }
8433
8434 #if ZIPPY
8435         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8436             first.initDone) {
8437           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8438                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8439                 SendToICS("draw ");
8440                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8441           }
8442           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8443           ics_user_moved = 1;
8444           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8445                 char buf[3*MSG_SIZ];
8446
8447                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8448                         programStats.score / 100.,
8449                         programStats.depth,
8450                         programStats.time / 100.,
8451                         (unsigned int)programStats.nodes,
8452                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8453                         programStats.movelist);
8454                 SendToICS(buf);
8455 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8456           }
8457         }
8458 #endif
8459
8460         /* [AS] Clear stats for next move */
8461         ClearProgramStats();
8462         thinkOutput[0] = NULLCHAR;
8463         hiddenThinkOutputState = 0;
8464
8465         bookHit = NULL;
8466         if (gameMode == TwoMachinesPlay) {
8467             /* [HGM] relaying draw offers moved to after reception of move */
8468             /* and interpreting offer as claim if it brings draw condition */
8469             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8470                 SendToProgram("draw\n", cps->other);
8471             }
8472             if (cps->other->sendTime) {
8473                 SendTimeRemaining(cps->other,
8474                                   cps->other->twoMachinesColor[0] == 'w');
8475             }
8476             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8477             if (firstMove && !bookHit) {
8478                 firstMove = FALSE;
8479                 if (cps->other->useColors) {
8480                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8481                 }
8482                 SendToProgram("go\n", cps->other);
8483             }
8484             cps->other->maybeThinking = TRUE;
8485         }
8486
8487         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8488
8489         if (!pausing && appData.ringBellAfterMoves) {
8490             RingBell();
8491         }
8492
8493         /*
8494          * Reenable menu items that were disabled while
8495          * machine was thinking
8496          */
8497         if (gameMode != TwoMachinesPlay)
8498             SetUserThinkingEnables();
8499
8500         // [HGM] book: after book hit opponent has received move and is now in force mode
8501         // force the book reply into it, and then fake that it outputted this move by jumping
8502         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8503         if(bookHit) {
8504                 static char bookMove[MSG_SIZ]; // a bit generous?
8505
8506                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8507                 strcat(bookMove, bookHit);
8508                 message = bookMove;
8509                 cps = cps->other;
8510                 programStats.nodes = programStats.depth = programStats.time =
8511                 programStats.score = programStats.got_only_move = 0;
8512                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8513
8514                 if(cps->lastPing != cps->lastPong) {
8515                     savedMessage = message; // args for deferred call
8516                     savedState = cps;
8517                     ScheduleDelayedEvent(DeferredBookMove, 10);
8518                     return;
8519                 }
8520                 goto FakeBookMove;
8521         }
8522
8523         return;
8524     }
8525
8526     /* Set special modes for chess engines.  Later something general
8527      *  could be added here; for now there is just one kludge feature,
8528      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8529      *  when "xboard" is given as an interactive command.
8530      */
8531     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8532         cps->useSigint = FALSE;
8533         cps->useSigterm = FALSE;
8534     }
8535     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8536       ParseFeatures(message+8, cps);
8537       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8538     }
8539
8540     if (!strncmp(message, "setup ", 6) && 
8541         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8542                                         ) { // [HGM] allow first engine to define opening position
8543       int dummy, s=6; char buf[MSG_SIZ];
8544       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8545       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8546       if(startedFromSetupPosition) return;
8547       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8548       ParseFEN(boards[0], &dummy, message+s);
8549       DrawPosition(TRUE, boards[0]);
8550       startedFromSetupPosition = TRUE;
8551       return;
8552     }
8553     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8554      * want this, I was asked to put it in, and obliged.
8555      */
8556     if (!strncmp(message, "setboard ", 9)) {
8557         Board initial_position;
8558
8559         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8560
8561         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8562             DisplayError(_("Bad FEN received from engine"), 0);
8563             return ;
8564         } else {
8565            Reset(TRUE, FALSE);
8566            CopyBoard(boards[0], initial_position);
8567            initialRulePlies = FENrulePlies;
8568            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8569            else gameMode = MachinePlaysBlack;
8570            DrawPosition(FALSE, boards[currentMove]);
8571         }
8572         return;
8573     }
8574
8575     /*
8576      * Look for communication commands
8577      */
8578     if (!strncmp(message, "telluser ", 9)) {
8579         if(message[9] == '\\' && message[10] == '\\')
8580             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8581         PlayTellSound();
8582         DisplayNote(message + 9);
8583         return;
8584     }
8585     if (!strncmp(message, "tellusererror ", 14)) {
8586         cps->userError = 1;
8587         if(message[14] == '\\' && message[15] == '\\')
8588             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8589         PlayTellSound();
8590         DisplayError(message + 14, 0);
8591         return;
8592     }
8593     if (!strncmp(message, "tellopponent ", 13)) {
8594       if (appData.icsActive) {
8595         if (loggedOn) {
8596           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8597           SendToICS(buf1);
8598         }
8599       } else {
8600         DisplayNote(message + 13);
8601       }
8602       return;
8603     }
8604     if (!strncmp(message, "tellothers ", 11)) {
8605       if (appData.icsActive) {
8606         if (loggedOn) {
8607           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8608           SendToICS(buf1);
8609         }
8610       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8611       return;
8612     }
8613     if (!strncmp(message, "tellall ", 8)) {
8614       if (appData.icsActive) {
8615         if (loggedOn) {
8616           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8617           SendToICS(buf1);
8618         }
8619       } else {
8620         DisplayNote(message + 8);
8621       }
8622       return;
8623     }
8624     if (strncmp(message, "warning", 7) == 0) {
8625         /* Undocumented feature, use tellusererror in new code */
8626         DisplayError(message, 0);
8627         return;
8628     }
8629     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8630         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8631         strcat(realname, " query");
8632         AskQuestion(realname, buf2, buf1, cps->pr);
8633         return;
8634     }
8635     /* Commands from the engine directly to ICS.  We don't allow these to be
8636      *  sent until we are logged on. Crafty kibitzes have been known to
8637      *  interfere with the login process.
8638      */
8639     if (loggedOn) {
8640         if (!strncmp(message, "tellics ", 8)) {
8641             SendToICS(message + 8);
8642             SendToICS("\n");
8643             return;
8644         }
8645         if (!strncmp(message, "tellicsnoalias ", 15)) {
8646             SendToICS(ics_prefix);
8647             SendToICS(message + 15);
8648             SendToICS("\n");
8649             return;
8650         }
8651         /* The following are for backward compatibility only */
8652         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8653             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8654             SendToICS(ics_prefix);
8655             SendToICS(message);
8656             SendToICS("\n");
8657             return;
8658         }
8659     }
8660     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8661         return;
8662     }
8663     if(!strncmp(message, "highlight ", 10)) {
8664         if(appData.testLegality && appData.markers) return;
8665         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8666         return;
8667     }
8668     /*
8669      * If the move is illegal, cancel it and redraw the board.
8670      * Also deal with other error cases.  Matching is rather loose
8671      * here to accommodate engines written before the spec.
8672      */
8673     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8674         strncmp(message, "Error", 5) == 0) {
8675         if (StrStr(message, "name") ||
8676             StrStr(message, "rating") || StrStr(message, "?") ||
8677             StrStr(message, "result") || StrStr(message, "board") ||
8678             StrStr(message, "bk") || StrStr(message, "computer") ||
8679             StrStr(message, "variant") || StrStr(message, "hint") ||
8680             StrStr(message, "random") || StrStr(message, "depth") ||
8681             StrStr(message, "accepted")) {
8682             return;
8683         }
8684         if (StrStr(message, "protover")) {
8685           /* Program is responding to input, so it's apparently done
8686              initializing, and this error message indicates it is
8687              protocol version 1.  So we don't need to wait any longer
8688              for it to initialize and send feature commands. */
8689           FeatureDone(cps, 1);
8690           cps->protocolVersion = 1;
8691           return;
8692         }
8693         cps->maybeThinking = FALSE;
8694
8695         if (StrStr(message, "draw")) {
8696             /* Program doesn't have "draw" command */
8697             cps->sendDrawOffers = 0;
8698             return;
8699         }
8700         if (cps->sendTime != 1 &&
8701             (StrStr(message, "time") || StrStr(message, "otim"))) {
8702           /* Program apparently doesn't have "time" or "otim" command */
8703           cps->sendTime = 0;
8704           return;
8705         }
8706         if (StrStr(message, "analyze")) {
8707             cps->analysisSupport = FALSE;
8708             cps->analyzing = FALSE;
8709 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8710             EditGameEvent(); // [HGM] try to preserve loaded game
8711             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8712             DisplayError(buf2, 0);
8713             return;
8714         }
8715         if (StrStr(message, "(no matching move)st")) {
8716           /* Special kludge for GNU Chess 4 only */
8717           cps->stKludge = TRUE;
8718           SendTimeControl(cps, movesPerSession, timeControl,
8719                           timeIncrement, appData.searchDepth,
8720                           searchTime);
8721           return;
8722         }
8723         if (StrStr(message, "(no matching move)sd")) {
8724           /* Special kludge for GNU Chess 4 only */
8725           cps->sdKludge = TRUE;
8726           SendTimeControl(cps, movesPerSession, timeControl,
8727                           timeIncrement, appData.searchDepth,
8728                           searchTime);
8729           return;
8730         }
8731         if (!StrStr(message, "llegal")) {
8732             return;
8733         }
8734         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8735             gameMode == IcsIdle) return;
8736         if (forwardMostMove <= backwardMostMove) return;
8737         if (pausing) PauseEvent();
8738       if(appData.forceIllegal) {
8739             // [HGM] illegal: machine refused move; force position after move into it
8740           SendToProgram("force\n", cps);
8741           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8742                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8743                 // when black is to move, while there might be nothing on a2 or black
8744                 // might already have the move. So send the board as if white has the move.
8745                 // But first we must change the stm of the engine, as it refused the last move
8746                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8747                 if(WhiteOnMove(forwardMostMove)) {
8748                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8749                     SendBoard(cps, forwardMostMove); // kludgeless board
8750                 } else {
8751                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8752                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8753                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8754                 }
8755           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8756             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8757                  gameMode == TwoMachinesPlay)
8758               SendToProgram("go\n", cps);
8759             return;
8760       } else
8761         if (gameMode == PlayFromGameFile) {
8762             /* Stop reading this game file */
8763             gameMode = EditGame;
8764             ModeHighlight();
8765         }
8766         /* [HGM] illegal-move claim should forfeit game when Xboard */
8767         /* only passes fully legal moves                            */
8768         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8769             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8770                                 "False illegal-move claim", GE_XBOARD );
8771             return; // do not take back move we tested as valid
8772         }
8773         currentMove = forwardMostMove-1;
8774         DisplayMove(currentMove-1); /* before DisplayMoveError */
8775         SwitchClocks(forwardMostMove-1); // [HGM] race
8776         DisplayBothClocks();
8777         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8778                 parseList[currentMove], _(cps->which));
8779         DisplayMoveError(buf1);
8780         DrawPosition(FALSE, boards[currentMove]);
8781
8782         SetUserThinkingEnables();
8783         return;
8784     }
8785     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8786         /* Program has a broken "time" command that
8787            outputs a string not ending in newline.
8788            Don't use it. */
8789         cps->sendTime = 0;
8790     }
8791
8792     /*
8793      * If chess program startup fails, exit with an error message.
8794      * Attempts to recover here are futile. [HGM] Well, we try anyway
8795      */
8796     if ((StrStr(message, "unknown host") != NULL)
8797         || (StrStr(message, "No remote directory") != NULL)
8798         || (StrStr(message, "not found") != NULL)
8799         || (StrStr(message, "No such file") != NULL)
8800         || (StrStr(message, "can't alloc") != NULL)
8801         || (StrStr(message, "Permission denied") != NULL)) {
8802
8803         cps->maybeThinking = FALSE;
8804         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8805                 _(cps->which), cps->program, cps->host, message);
8806         RemoveInputSource(cps->isr);
8807         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8808             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8809             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8810         }
8811         return;
8812     }
8813
8814     /*
8815      * Look for hint output
8816      */
8817     if (sscanf(message, "Hint: %s", buf1) == 1) {
8818         if (cps == &first && hintRequested) {
8819             hintRequested = FALSE;
8820             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8821                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8822                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8823                                     PosFlags(forwardMostMove),
8824                                     fromY, fromX, toY, toX, promoChar, buf1);
8825                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8826                 DisplayInformation(buf2);
8827             } else {
8828                 /* Hint move could not be parsed!? */
8829               snprintf(buf2, sizeof(buf2),
8830                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8831                         buf1, _(cps->which));
8832                 DisplayError(buf2, 0);
8833             }
8834         } else {
8835           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8836         }
8837         return;
8838     }
8839
8840     /*
8841      * Ignore other messages if game is not in progress
8842      */
8843     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8844         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8845
8846     /*
8847      * look for win, lose, draw, or draw offer
8848      */
8849     if (strncmp(message, "1-0", 3) == 0) {
8850         char *p, *q, *r = "";
8851         p = strchr(message, '{');
8852         if (p) {
8853             q = strchr(p, '}');
8854             if (q) {
8855                 *q = NULLCHAR;
8856                 r = p + 1;
8857             }
8858         }
8859         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8860         return;
8861     } else if (strncmp(message, "0-1", 3) == 0) {
8862         char *p, *q, *r = "";
8863         p = strchr(message, '{');
8864         if (p) {
8865             q = strchr(p, '}');
8866             if (q) {
8867                 *q = NULLCHAR;
8868                 r = p + 1;
8869             }
8870         }
8871         /* Kludge for Arasan 4.1 bug */
8872         if (strcmp(r, "Black resigns") == 0) {
8873             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8874             return;
8875         }
8876         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8877         return;
8878     } else if (strncmp(message, "1/2", 3) == 0) {
8879         char *p, *q, *r = "";
8880         p = strchr(message, '{');
8881         if (p) {
8882             q = strchr(p, '}');
8883             if (q) {
8884                 *q = NULLCHAR;
8885                 r = p + 1;
8886             }
8887         }
8888
8889         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8890         return;
8891
8892     } else if (strncmp(message, "White resign", 12) == 0) {
8893         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8894         return;
8895     } else if (strncmp(message, "Black resign", 12) == 0) {
8896         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8897         return;
8898     } else if (strncmp(message, "White matches", 13) == 0 ||
8899                strncmp(message, "Black matches", 13) == 0   ) {
8900         /* [HGM] ignore GNUShogi noises */
8901         return;
8902     } else if (strncmp(message, "White", 5) == 0 &&
8903                message[5] != '(' &&
8904                StrStr(message, "Black") == NULL) {
8905         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8906         return;
8907     } else if (strncmp(message, "Black", 5) == 0 &&
8908                message[5] != '(') {
8909         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8910         return;
8911     } else if (strcmp(message, "resign") == 0 ||
8912                strcmp(message, "computer resigns") == 0) {
8913         switch (gameMode) {
8914           case MachinePlaysBlack:
8915           case IcsPlayingBlack:
8916             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8917             break;
8918           case MachinePlaysWhite:
8919           case IcsPlayingWhite:
8920             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8921             break;
8922           case TwoMachinesPlay:
8923             if (cps->twoMachinesColor[0] == 'w')
8924               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8925             else
8926               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8927             break;
8928           default:
8929             /* can't happen */
8930             break;
8931         }
8932         return;
8933     } else if (strncmp(message, "opponent mates", 14) == 0) {
8934         switch (gameMode) {
8935           case MachinePlaysBlack:
8936           case IcsPlayingBlack:
8937             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8938             break;
8939           case MachinePlaysWhite:
8940           case IcsPlayingWhite:
8941             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8942             break;
8943           case TwoMachinesPlay:
8944             if (cps->twoMachinesColor[0] == 'w')
8945               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8946             else
8947               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8948             break;
8949           default:
8950             /* can't happen */
8951             break;
8952         }
8953         return;
8954     } else if (strncmp(message, "computer mates", 14) == 0) {
8955         switch (gameMode) {
8956           case MachinePlaysBlack:
8957           case IcsPlayingBlack:
8958             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8959             break;
8960           case MachinePlaysWhite:
8961           case IcsPlayingWhite:
8962             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8963             break;
8964           case TwoMachinesPlay:
8965             if (cps->twoMachinesColor[0] == 'w')
8966               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8967             else
8968               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8969             break;
8970           default:
8971             /* can't happen */
8972             break;
8973         }
8974         return;
8975     } else if (strncmp(message, "checkmate", 9) == 0) {
8976         if (WhiteOnMove(forwardMostMove)) {
8977             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8978         } else {
8979             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8980         }
8981         return;
8982     } else if (strstr(message, "Draw") != NULL ||
8983                strstr(message, "game is a draw") != NULL) {
8984         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8985         return;
8986     } else if (strstr(message, "offer") != NULL &&
8987                strstr(message, "draw") != NULL) {
8988 #if ZIPPY
8989         if (appData.zippyPlay && first.initDone) {
8990             /* Relay offer to ICS */
8991             SendToICS(ics_prefix);
8992             SendToICS("draw\n");
8993         }
8994 #endif
8995         cps->offeredDraw = 2; /* valid until this engine moves twice */
8996         if (gameMode == TwoMachinesPlay) {
8997             if (cps->other->offeredDraw) {
8998                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8999             /* [HGM] in two-machine mode we delay relaying draw offer      */
9000             /* until after we also have move, to see if it is really claim */
9001             }
9002         } else if (gameMode == MachinePlaysWhite ||
9003                    gameMode == MachinePlaysBlack) {
9004           if (userOfferedDraw) {
9005             DisplayInformation(_("Machine accepts your draw offer"));
9006             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9007           } else {
9008             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9009           }
9010         }
9011     }
9012
9013
9014     /*
9015      * Look for thinking output
9016      */
9017     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9018           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9019                                 ) {
9020         int plylev, mvleft, mvtot, curscore, time;
9021         char mvname[MOVE_LEN];
9022         u64 nodes; // [DM]
9023         char plyext;
9024         int ignore = FALSE;
9025         int prefixHint = FALSE;
9026         mvname[0] = NULLCHAR;
9027
9028         switch (gameMode) {
9029           case MachinePlaysBlack:
9030           case IcsPlayingBlack:
9031             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9032             break;
9033           case MachinePlaysWhite:
9034           case IcsPlayingWhite:
9035             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9036             break;
9037           case AnalyzeMode:
9038           case AnalyzeFile:
9039             break;
9040           case IcsObserving: /* [DM] icsEngineAnalyze */
9041             if (!appData.icsEngineAnalyze) ignore = TRUE;
9042             break;
9043           case TwoMachinesPlay:
9044             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9045                 ignore = TRUE;
9046             }
9047             break;
9048           default:
9049             ignore = TRUE;
9050             break;
9051         }
9052
9053         if (!ignore) {
9054             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9055             buf1[0] = NULLCHAR;
9056             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9057                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9058
9059                 if (plyext != ' ' && plyext != '\t') {
9060                     time *= 100;
9061                 }
9062
9063                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9064                 if( cps->scoreIsAbsolute &&
9065                     ( gameMode == MachinePlaysBlack ||
9066                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9067                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9068                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9069                      !WhiteOnMove(currentMove)
9070                     ) )
9071                 {
9072                     curscore = -curscore;
9073                 }
9074
9075                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9076
9077                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9078                         char buf[MSG_SIZ];
9079                         FILE *f;
9080                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9081                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9082                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9083                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9084                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9085                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9086                                 fclose(f);
9087                         } else DisplayError(_("failed writing PV"), 0);
9088                 }
9089
9090                 tempStats.depth = plylev;
9091                 tempStats.nodes = nodes;
9092                 tempStats.time = time;
9093                 tempStats.score = curscore;
9094                 tempStats.got_only_move = 0;
9095
9096                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9097                         int ticklen;
9098
9099                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9100                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9101                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9102                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9103                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9104                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9105                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9106                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9107                 }
9108
9109                 /* Buffer overflow protection */
9110                 if (pv[0] != NULLCHAR) {
9111                     if (strlen(pv) >= sizeof(tempStats.movelist)
9112                         && appData.debugMode) {
9113                         fprintf(debugFP,
9114                                 "PV is too long; using the first %u bytes.\n",
9115                                 (unsigned) sizeof(tempStats.movelist) - 1);
9116                     }
9117
9118                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9119                 } else {
9120                     sprintf(tempStats.movelist, " no PV\n");
9121                 }
9122
9123                 if (tempStats.seen_stat) {
9124                     tempStats.ok_to_send = 1;
9125                 }
9126
9127                 if (strchr(tempStats.movelist, '(') != NULL) {
9128                     tempStats.line_is_book = 1;
9129                     tempStats.nr_moves = 0;
9130                     tempStats.moves_left = 0;
9131                 } else {
9132                     tempStats.line_is_book = 0;
9133                 }
9134
9135                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9136                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9137
9138                 SendProgramStatsToFrontend( cps, &tempStats );
9139
9140                 /*
9141                     [AS] Protect the thinkOutput buffer from overflow... this
9142                     is only useful if buf1 hasn't overflowed first!
9143                 */
9144                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9145                          plylev,
9146                          (gameMode == TwoMachinesPlay ?
9147                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9148                          ((double) curscore) / 100.0,
9149                          prefixHint ? lastHint : "",
9150                          prefixHint ? " " : "" );
9151
9152                 if( buf1[0] != NULLCHAR ) {
9153                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9154
9155                     if( strlen(pv) > max_len ) {
9156                         if( appData.debugMode) {
9157                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9158                         }
9159                         pv[max_len+1] = '\0';
9160                     }
9161
9162                     strcat( thinkOutput, pv);
9163                 }
9164
9165                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9166                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9167                     DisplayMove(currentMove - 1);
9168                 }
9169                 return;
9170
9171             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9172                 /* crafty (9.25+) says "(only move) <move>"
9173                  * if there is only 1 legal move
9174                  */
9175                 sscanf(p, "(only move) %s", buf1);
9176                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9177                 sprintf(programStats.movelist, "%s (only move)", buf1);
9178                 programStats.depth = 1;
9179                 programStats.nr_moves = 1;
9180                 programStats.moves_left = 1;
9181                 programStats.nodes = 1;
9182                 programStats.time = 1;
9183                 programStats.got_only_move = 1;
9184
9185                 /* Not really, but we also use this member to
9186                    mean "line isn't going to change" (Crafty
9187                    isn't searching, so stats won't change) */
9188                 programStats.line_is_book = 1;
9189
9190                 SendProgramStatsToFrontend( cps, &programStats );
9191
9192                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9193                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9194                     DisplayMove(currentMove - 1);
9195                 }
9196                 return;
9197             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9198                               &time, &nodes, &plylev, &mvleft,
9199                               &mvtot, mvname) >= 5) {
9200                 /* The stat01: line is from Crafty (9.29+) in response
9201                    to the "." command */
9202                 programStats.seen_stat = 1;
9203                 cps->maybeThinking = TRUE;
9204
9205                 if (programStats.got_only_move || !appData.periodicUpdates)
9206                   return;
9207
9208                 programStats.depth = plylev;
9209                 programStats.time = time;
9210                 programStats.nodes = nodes;
9211                 programStats.moves_left = mvleft;
9212                 programStats.nr_moves = mvtot;
9213                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9214                 programStats.ok_to_send = 1;
9215                 programStats.movelist[0] = '\0';
9216
9217                 SendProgramStatsToFrontend( cps, &programStats );
9218
9219                 return;
9220
9221             } else if (strncmp(message,"++",2) == 0) {
9222                 /* Crafty 9.29+ outputs this */
9223                 programStats.got_fail = 2;
9224                 return;
9225
9226             } else if (strncmp(message,"--",2) == 0) {
9227                 /* Crafty 9.29+ outputs this */
9228                 programStats.got_fail = 1;
9229                 return;
9230
9231             } else if (thinkOutput[0] != NULLCHAR &&
9232                        strncmp(message, "    ", 4) == 0) {
9233                 unsigned message_len;
9234
9235                 p = message;
9236                 while (*p && *p == ' ') p++;
9237
9238                 message_len = strlen( p );
9239
9240                 /* [AS] Avoid buffer overflow */
9241                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9242                     strcat(thinkOutput, " ");
9243                     strcat(thinkOutput, p);
9244                 }
9245
9246                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9247                     strcat(programStats.movelist, " ");
9248                     strcat(programStats.movelist, p);
9249                 }
9250
9251                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9252                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9253                     DisplayMove(currentMove - 1);
9254                 }
9255                 return;
9256             }
9257         }
9258         else {
9259             buf1[0] = NULLCHAR;
9260
9261             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9262                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9263             {
9264                 ChessProgramStats cpstats;
9265
9266                 if (plyext != ' ' && plyext != '\t') {
9267                     time *= 100;
9268                 }
9269
9270                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9271                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9272                     curscore = -curscore;
9273                 }
9274
9275                 cpstats.depth = plylev;
9276                 cpstats.nodes = nodes;
9277                 cpstats.time = time;
9278                 cpstats.score = curscore;
9279                 cpstats.got_only_move = 0;
9280                 cpstats.movelist[0] = '\0';
9281
9282                 if (buf1[0] != NULLCHAR) {
9283                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9284                 }
9285
9286                 cpstats.ok_to_send = 0;
9287                 cpstats.line_is_book = 0;
9288                 cpstats.nr_moves = 0;
9289                 cpstats.moves_left = 0;
9290
9291                 SendProgramStatsToFrontend( cps, &cpstats );
9292             }
9293         }
9294     }
9295 }
9296
9297
9298 /* Parse a game score from the character string "game", and
9299    record it as the history of the current game.  The game
9300    score is NOT assumed to start from the standard position.
9301    The display is not updated in any way.
9302    */
9303 void
9304 ParseGameHistory (char *game)
9305 {
9306     ChessMove moveType;
9307     int fromX, fromY, toX, toY, boardIndex;
9308     char promoChar;
9309     char *p, *q;
9310     char buf[MSG_SIZ];
9311
9312     if (appData.debugMode)
9313       fprintf(debugFP, "Parsing game history: %s\n", game);
9314
9315     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9316     gameInfo.site = StrSave(appData.icsHost);
9317     gameInfo.date = PGNDate();
9318     gameInfo.round = StrSave("-");
9319
9320     /* Parse out names of players */
9321     while (*game == ' ') game++;
9322     p = buf;
9323     while (*game != ' ') *p++ = *game++;
9324     *p = NULLCHAR;
9325     gameInfo.white = StrSave(buf);
9326     while (*game == ' ') game++;
9327     p = buf;
9328     while (*game != ' ' && *game != '\n') *p++ = *game++;
9329     *p = NULLCHAR;
9330     gameInfo.black = StrSave(buf);
9331
9332     /* Parse moves */
9333     boardIndex = blackPlaysFirst ? 1 : 0;
9334     yynewstr(game);
9335     for (;;) {
9336         yyboardindex = boardIndex;
9337         moveType = (ChessMove) Myylex();
9338         switch (moveType) {
9339           case IllegalMove:             /* maybe suicide chess, etc. */
9340   if (appData.debugMode) {
9341     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9342     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9343     setbuf(debugFP, NULL);
9344   }
9345           case WhitePromotion:
9346           case BlackPromotion:
9347           case WhiteNonPromotion:
9348           case BlackNonPromotion:
9349           case NormalMove:
9350           case WhiteCapturesEnPassant:
9351           case BlackCapturesEnPassant:
9352           case WhiteKingSideCastle:
9353           case WhiteQueenSideCastle:
9354           case BlackKingSideCastle:
9355           case BlackQueenSideCastle:
9356           case WhiteKingSideCastleWild:
9357           case WhiteQueenSideCastleWild:
9358           case BlackKingSideCastleWild:
9359           case BlackQueenSideCastleWild:
9360           /* PUSH Fabien */
9361           case WhiteHSideCastleFR:
9362           case WhiteASideCastleFR:
9363           case BlackHSideCastleFR:
9364           case BlackASideCastleFR:
9365           /* POP Fabien */
9366             fromX = currentMoveString[0] - AAA;
9367             fromY = currentMoveString[1] - ONE;
9368             toX = currentMoveString[2] - AAA;
9369             toY = currentMoveString[3] - ONE;
9370             promoChar = currentMoveString[4];
9371             break;
9372           case WhiteDrop:
9373           case BlackDrop:
9374             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9375             fromX = moveType == WhiteDrop ?
9376               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9377             (int) CharToPiece(ToLower(currentMoveString[0]));
9378             fromY = DROP_RANK;
9379             toX = currentMoveString[2] - AAA;
9380             toY = currentMoveString[3] - ONE;
9381             promoChar = NULLCHAR;
9382             break;
9383           case AmbiguousMove:
9384             /* bug? */
9385             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9386   if (appData.debugMode) {
9387     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9388     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9389     setbuf(debugFP, NULL);
9390   }
9391             DisplayError(buf, 0);
9392             return;
9393           case ImpossibleMove:
9394             /* bug? */
9395             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9396   if (appData.debugMode) {
9397     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9398     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9399     setbuf(debugFP, NULL);
9400   }
9401             DisplayError(buf, 0);
9402             return;
9403           case EndOfFile:
9404             if (boardIndex < backwardMostMove) {
9405                 /* Oops, gap.  How did that happen? */
9406                 DisplayError(_("Gap in move list"), 0);
9407                 return;
9408             }
9409             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9410             if (boardIndex > forwardMostMove) {
9411                 forwardMostMove = boardIndex;
9412             }
9413             return;
9414           case ElapsedTime:
9415             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9416                 strcat(parseList[boardIndex-1], " ");
9417                 strcat(parseList[boardIndex-1], yy_text);
9418             }
9419             continue;
9420           case Comment:
9421           case PGNTag:
9422           case NAG:
9423           default:
9424             /* ignore */
9425             continue;
9426           case WhiteWins:
9427           case BlackWins:
9428           case GameIsDrawn:
9429           case GameUnfinished:
9430             if (gameMode == IcsExamining) {
9431                 if (boardIndex < backwardMostMove) {
9432                     /* Oops, gap.  How did that happen? */
9433                     return;
9434                 }
9435                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9436                 return;
9437             }
9438             gameInfo.result = moveType;
9439             p = strchr(yy_text, '{');
9440             if (p == NULL) p = strchr(yy_text, '(');
9441             if (p == NULL) {
9442                 p = yy_text;
9443                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9444             } else {
9445                 q = strchr(p, *p == '{' ? '}' : ')');
9446                 if (q != NULL) *q = NULLCHAR;
9447                 p++;
9448             }
9449             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9450             gameInfo.resultDetails = StrSave(p);
9451             continue;
9452         }
9453         if (boardIndex >= forwardMostMove &&
9454             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9455             backwardMostMove = blackPlaysFirst ? 1 : 0;
9456             return;
9457         }
9458         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9459                                  fromY, fromX, toY, toX, promoChar,
9460                                  parseList[boardIndex]);
9461         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9462         /* currentMoveString is set as a side-effect of yylex */
9463         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9464         strcat(moveList[boardIndex], "\n");
9465         boardIndex++;
9466         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9467         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9468           case MT_NONE:
9469           case MT_STALEMATE:
9470           default:
9471             break;
9472           case MT_CHECK:
9473             if(gameInfo.variant != VariantShogi)
9474                 strcat(parseList[boardIndex - 1], "+");
9475             break;
9476           case MT_CHECKMATE:
9477           case MT_STAINMATE:
9478             strcat(parseList[boardIndex - 1], "#");
9479             break;
9480         }
9481     }
9482 }
9483
9484
9485 /* Apply a move to the given board  */
9486 void
9487 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9488 {
9489   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9490   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9491
9492     /* [HGM] compute & store e.p. status and castling rights for new position */
9493     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9494
9495       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9496       oldEP = (signed char)board[EP_STATUS];
9497       board[EP_STATUS] = EP_NONE;
9498
9499   if (fromY == DROP_RANK) {
9500         /* must be first */
9501         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9502             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9503             return;
9504         }
9505         piece = board[toY][toX] = (ChessSquare) fromX;
9506   } else {
9507       int i;
9508
9509       if( board[toY][toX] != EmptySquare )
9510            board[EP_STATUS] = EP_CAPTURE;
9511
9512       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9513            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9514                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9515       } else
9516       if( board[fromY][fromX] == WhitePawn ) {
9517            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9518                board[EP_STATUS] = EP_PAWN_MOVE;
9519            if( toY-fromY==2) {
9520                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9521                         gameInfo.variant != VariantBerolina || toX < fromX)
9522                       board[EP_STATUS] = toX | berolina;
9523                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9524                         gameInfo.variant != VariantBerolina || toX > fromX)
9525                       board[EP_STATUS] = toX;
9526            }
9527       } else
9528       if( board[fromY][fromX] == BlackPawn ) {
9529            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9530                board[EP_STATUS] = EP_PAWN_MOVE;
9531            if( toY-fromY== -2) {
9532                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9533                         gameInfo.variant != VariantBerolina || toX < fromX)
9534                       board[EP_STATUS] = toX | berolina;
9535                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9536                         gameInfo.variant != VariantBerolina || toX > fromX)
9537                       board[EP_STATUS] = toX;
9538            }
9539        }
9540
9541        for(i=0; i<nrCastlingRights; i++) {
9542            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9543               board[CASTLING][i] == toX   && castlingRank[i] == toY
9544              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9545        }
9546
9547        if(gameInfo.variant == VariantSChess) { // update virginity
9548            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9549            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9550            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9551            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9552        }
9553
9554      if (fromX == toX && fromY == toY) return;
9555
9556      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9557      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9558      if(gameInfo.variant == VariantKnightmate)
9559          king += (int) WhiteUnicorn - (int) WhiteKing;
9560
9561     /* Code added by Tord: */
9562     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9563     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9564         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9565       board[fromY][fromX] = EmptySquare;
9566       board[toY][toX] = EmptySquare;
9567       if((toX > fromX) != (piece == WhiteRook)) {
9568         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9569       } else {
9570         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9571       }
9572     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9573                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9574       board[fromY][fromX] = EmptySquare;
9575       board[toY][toX] = EmptySquare;
9576       if((toX > fromX) != (piece == BlackRook)) {
9577         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9578       } else {
9579         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9580       }
9581     /* End of code added by Tord */
9582
9583     } else if (board[fromY][fromX] == king
9584         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9585         && toY == fromY && toX > fromX+1) {
9586         board[fromY][fromX] = EmptySquare;
9587         board[toY][toX] = king;
9588         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9589         board[fromY][BOARD_RGHT-1] = EmptySquare;
9590     } else if (board[fromY][fromX] == king
9591         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9592                && toY == fromY && toX < fromX-1) {
9593         board[fromY][fromX] = EmptySquare;
9594         board[toY][toX] = king;
9595         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9596         board[fromY][BOARD_LEFT] = EmptySquare;
9597     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9598                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9599                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9600                ) {
9601         /* white pawn promotion */
9602         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9603         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9604             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9605         board[fromY][fromX] = EmptySquare;
9606     } else if ((fromY >= BOARD_HEIGHT>>1)
9607                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9608                && (toX != fromX)
9609                && gameInfo.variant != VariantXiangqi
9610                && gameInfo.variant != VariantBerolina
9611                && (board[fromY][fromX] == WhitePawn)
9612                && (board[toY][toX] == EmptySquare)) {
9613         board[fromY][fromX] = EmptySquare;
9614         board[toY][toX] = WhitePawn;
9615         captured = board[toY - 1][toX];
9616         board[toY - 1][toX] = EmptySquare;
9617     } else if ((fromY == BOARD_HEIGHT-4)
9618                && (toX == fromX)
9619                && gameInfo.variant == VariantBerolina
9620                && (board[fromY][fromX] == WhitePawn)
9621                && (board[toY][toX] == EmptySquare)) {
9622         board[fromY][fromX] = EmptySquare;
9623         board[toY][toX] = WhitePawn;
9624         if(oldEP & EP_BEROLIN_A) {
9625                 captured = board[fromY][fromX-1];
9626                 board[fromY][fromX-1] = EmptySquare;
9627         }else{  captured = board[fromY][fromX+1];
9628                 board[fromY][fromX+1] = EmptySquare;
9629         }
9630     } else if (board[fromY][fromX] == king
9631         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9632                && toY == fromY && toX > fromX+1) {
9633         board[fromY][fromX] = EmptySquare;
9634         board[toY][toX] = king;
9635         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9636         board[fromY][BOARD_RGHT-1] = EmptySquare;
9637     } else if (board[fromY][fromX] == king
9638         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9639                && toY == fromY && toX < fromX-1) {
9640         board[fromY][fromX] = EmptySquare;
9641         board[toY][toX] = king;
9642         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9643         board[fromY][BOARD_LEFT] = EmptySquare;
9644     } else if (fromY == 7 && fromX == 3
9645                && board[fromY][fromX] == BlackKing
9646                && toY == 7 && toX == 5) {
9647         board[fromY][fromX] = EmptySquare;
9648         board[toY][toX] = BlackKing;
9649         board[fromY][7] = EmptySquare;
9650         board[toY][4] = BlackRook;
9651     } else if (fromY == 7 && fromX == 3
9652                && board[fromY][fromX] == BlackKing
9653                && toY == 7 && toX == 1) {
9654         board[fromY][fromX] = EmptySquare;
9655         board[toY][toX] = BlackKing;
9656         board[fromY][0] = EmptySquare;
9657         board[toY][2] = BlackRook;
9658     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9659                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9660                && toY < promoRank && promoChar
9661                ) {
9662         /* black pawn promotion */
9663         board[toY][toX] = CharToPiece(ToLower(promoChar));
9664         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9665             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9666         board[fromY][fromX] = EmptySquare;
9667     } else if ((fromY < BOARD_HEIGHT>>1)
9668                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9669                && (toX != fromX)
9670                && gameInfo.variant != VariantXiangqi
9671                && gameInfo.variant != VariantBerolina
9672                && (board[fromY][fromX] == BlackPawn)
9673                && (board[toY][toX] == EmptySquare)) {
9674         board[fromY][fromX] = EmptySquare;
9675         board[toY][toX] = BlackPawn;
9676         captured = board[toY + 1][toX];
9677         board[toY + 1][toX] = EmptySquare;
9678     } else if ((fromY == 3)
9679                && (toX == fromX)
9680                && gameInfo.variant == VariantBerolina
9681                && (board[fromY][fromX] == BlackPawn)
9682                && (board[toY][toX] == EmptySquare)) {
9683         board[fromY][fromX] = EmptySquare;
9684         board[toY][toX] = BlackPawn;
9685         if(oldEP & EP_BEROLIN_A) {
9686                 captured = board[fromY][fromX-1];
9687                 board[fromY][fromX-1] = EmptySquare;
9688         }else{  captured = board[fromY][fromX+1];
9689                 board[fromY][fromX+1] = EmptySquare;
9690         }
9691     } else {
9692         board[toY][toX] = board[fromY][fromX];
9693         board[fromY][fromX] = EmptySquare;
9694     }
9695   }
9696
9697     if (gameInfo.holdingsWidth != 0) {
9698
9699       /* !!A lot more code needs to be written to support holdings  */
9700       /* [HGM] OK, so I have written it. Holdings are stored in the */
9701       /* penultimate board files, so they are automaticlly stored   */
9702       /* in the game history.                                       */
9703       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9704                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9705         /* Delete from holdings, by decreasing count */
9706         /* and erasing image if necessary            */
9707         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9708         if(p < (int) BlackPawn) { /* white drop */
9709              p -= (int)WhitePawn;
9710                  p = PieceToNumber((ChessSquare)p);
9711              if(p >= gameInfo.holdingsSize) p = 0;
9712              if(--board[p][BOARD_WIDTH-2] <= 0)
9713                   board[p][BOARD_WIDTH-1] = EmptySquare;
9714              if((int)board[p][BOARD_WIDTH-2] < 0)
9715                         board[p][BOARD_WIDTH-2] = 0;
9716         } else {                  /* black drop */
9717              p -= (int)BlackPawn;
9718                  p = PieceToNumber((ChessSquare)p);
9719              if(p >= gameInfo.holdingsSize) p = 0;
9720              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9721                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9722              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9723                         board[BOARD_HEIGHT-1-p][1] = 0;
9724         }
9725       }
9726       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9727           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9728         /* [HGM] holdings: Add to holdings, if holdings exist */
9729         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9730                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9731                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9732         }
9733         p = (int) captured;
9734         if (p >= (int) BlackPawn) {
9735           p -= (int)BlackPawn;
9736           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9737                   /* in Shogi restore piece to its original  first */
9738                   captured = (ChessSquare) (DEMOTED captured);
9739                   p = DEMOTED p;
9740           }
9741           p = PieceToNumber((ChessSquare)p);
9742           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9743           board[p][BOARD_WIDTH-2]++;
9744           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9745         } else {
9746           p -= (int)WhitePawn;
9747           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9748                   captured = (ChessSquare) (DEMOTED captured);
9749                   p = DEMOTED p;
9750           }
9751           p = PieceToNumber((ChessSquare)p);
9752           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9753           board[BOARD_HEIGHT-1-p][1]++;
9754           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9755         }
9756       }
9757     } else if (gameInfo.variant == VariantAtomic) {
9758       if (captured != EmptySquare) {
9759         int y, x;
9760         for (y = toY-1; y <= toY+1; y++) {
9761           for (x = toX-1; x <= toX+1; x++) {
9762             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9763                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9764               board[y][x] = EmptySquare;
9765             }
9766           }
9767         }
9768         board[toY][toX] = EmptySquare;
9769       }
9770     }
9771     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9772         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9773     } else
9774     if(promoChar == '+') {
9775         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9776         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9777     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9778         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9779         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9780            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9781         board[toY][toX] = newPiece;
9782     }
9783     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9784                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9785         // [HGM] superchess: take promotion piece out of holdings
9786         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9787         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9788             if(!--board[k][BOARD_WIDTH-2])
9789                 board[k][BOARD_WIDTH-1] = EmptySquare;
9790         } else {
9791             if(!--board[BOARD_HEIGHT-1-k][1])
9792                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9793         }
9794     }
9795
9796 }
9797
9798 /* Updates forwardMostMove */
9799 void
9800 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9801 {
9802 //    forwardMostMove++; // [HGM] bare: moved downstream
9803
9804     (void) CoordsToAlgebraic(boards[forwardMostMove],
9805                              PosFlags(forwardMostMove),
9806                              fromY, fromX, toY, toX, promoChar,
9807                              parseList[forwardMostMove]);
9808
9809     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9810         int timeLeft; static int lastLoadFlag=0; int king, piece;
9811         piece = boards[forwardMostMove][fromY][fromX];
9812         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9813         if(gameInfo.variant == VariantKnightmate)
9814             king += (int) WhiteUnicorn - (int) WhiteKing;
9815         if(forwardMostMove == 0) {
9816             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9817                 fprintf(serverMoves, "%s;", UserName());
9818             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9819                 fprintf(serverMoves, "%s;", second.tidy);
9820             fprintf(serverMoves, "%s;", first.tidy);
9821             if(gameMode == MachinePlaysWhite)
9822                 fprintf(serverMoves, "%s;", UserName());
9823             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9824                 fprintf(serverMoves, "%s;", second.tidy);
9825         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9826         lastLoadFlag = loadFlag;
9827         // print base move
9828         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9829         // print castling suffix
9830         if( toY == fromY && piece == king ) {
9831             if(toX-fromX > 1)
9832                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9833             if(fromX-toX >1)
9834                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9835         }
9836         // e.p. suffix
9837         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9838              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9839              boards[forwardMostMove][toY][toX] == EmptySquare
9840              && fromX != toX && fromY != toY)
9841                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9842         // promotion suffix
9843         if(promoChar != NULLCHAR) {
9844             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9845                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9846                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9847             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9848         }
9849         if(!loadFlag) {
9850                 char buf[MOVE_LEN*2], *p; int len;
9851             fprintf(serverMoves, "/%d/%d",
9852                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9853             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9854             else                      timeLeft = blackTimeRemaining/1000;
9855             fprintf(serverMoves, "/%d", timeLeft);
9856                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9857                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9858                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9859                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9860             fprintf(serverMoves, "/%s", buf);
9861         }
9862         fflush(serverMoves);
9863     }
9864
9865     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9866         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9867       return;
9868     }
9869     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9870     if (commentList[forwardMostMove+1] != NULL) {
9871         free(commentList[forwardMostMove+1]);
9872         commentList[forwardMostMove+1] = NULL;
9873     }
9874     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9875     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9876     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9877     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9878     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9879     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9880     adjustedClock = FALSE;
9881     gameInfo.result = GameUnfinished;
9882     if (gameInfo.resultDetails != NULL) {
9883         free(gameInfo.resultDetails);
9884         gameInfo.resultDetails = NULL;
9885     }
9886     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9887                               moveList[forwardMostMove - 1]);
9888     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9889       case MT_NONE:
9890       case MT_STALEMATE:
9891       default:
9892         break;
9893       case MT_CHECK:
9894         if(gameInfo.variant != VariantShogi)
9895             strcat(parseList[forwardMostMove - 1], "+");
9896         break;
9897       case MT_CHECKMATE:
9898       case MT_STAINMATE:
9899         strcat(parseList[forwardMostMove - 1], "#");
9900         break;
9901     }
9902
9903 }
9904
9905 /* Updates currentMove if not pausing */
9906 void
9907 ShowMove (int fromX, int fromY, int toX, int toY)
9908 {
9909     int instant = (gameMode == PlayFromGameFile) ?
9910         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9911     if(appData.noGUI) return;
9912     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9913         if (!instant) {
9914             if (forwardMostMove == currentMove + 1) {
9915                 AnimateMove(boards[forwardMostMove - 1],
9916                             fromX, fromY, toX, toY);
9917             }
9918         }
9919         currentMove = forwardMostMove;
9920     }
9921
9922     if (instant) return;
9923
9924     DisplayMove(currentMove - 1);
9925     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9926             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9927                 SetHighlights(fromX, fromY, toX, toY);
9928             }
9929     }
9930     DrawPosition(FALSE, boards[currentMove]);
9931     DisplayBothClocks();
9932     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9933 }
9934
9935 void
9936 SendEgtPath (ChessProgramState *cps)
9937 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9938         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9939
9940         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9941
9942         while(*p) {
9943             char c, *q = name+1, *r, *s;
9944
9945             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9946             while(*p && *p != ',') *q++ = *p++;
9947             *q++ = ':'; *q = 0;
9948             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9949                 strcmp(name, ",nalimov:") == 0 ) {
9950                 // take nalimov path from the menu-changeable option first, if it is defined
9951               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9952                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9953             } else
9954             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9955                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9956                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9957                 s = r = StrStr(s, ":") + 1; // beginning of path info
9958                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9959                 c = *r; *r = 0;             // temporarily null-terminate path info
9960                     *--q = 0;               // strip of trailig ':' from name
9961                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9962                 *r = c;
9963                 SendToProgram(buf,cps);     // send egtbpath command for this format
9964             }
9965             if(*p == ',') p++; // read away comma to position for next format name
9966         }
9967 }
9968
9969 static int
9970 NonStandardBoardSize ()
9971 {
9972       /* [HGM] Awkward testing. Should really be a table */
9973       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9974       if( gameInfo.variant == VariantXiangqi )
9975            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9976       if( gameInfo.variant == VariantShogi )
9977            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9978       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9979            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9980       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9981           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9982            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9983       if( gameInfo.variant == VariantCourier )
9984            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9985       if( gameInfo.variant == VariantSuper )
9986            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9987       if( gameInfo.variant == VariantGreat )
9988            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9989       if( gameInfo.variant == VariantSChess )
9990            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9991       if( gameInfo.variant == VariantGrand )
9992            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9993       return overruled;
9994 }
9995
9996 void
9997 InitChessProgram (ChessProgramState *cps, int setup)
9998 /* setup needed to setup FRC opening position */
9999 {
10000     char buf[MSG_SIZ], b[MSG_SIZ];
10001     if (appData.noChessProgram) return;
10002     hintRequested = FALSE;
10003     bookRequested = FALSE;
10004
10005     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10006     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10007     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10008     if(cps->memSize) { /* [HGM] memory */
10009       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10010         SendToProgram(buf, cps);
10011     }
10012     SendEgtPath(cps); /* [HGM] EGT */
10013     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10014       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10015         SendToProgram(buf, cps);
10016     }
10017
10018     SendToProgram(cps->initString, cps);
10019     if (gameInfo.variant != VariantNormal &&
10020         gameInfo.variant != VariantLoadable
10021         /* [HGM] also send variant if board size non-standard */
10022         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10023                                             ) {
10024       char *v = VariantName(gameInfo.variant);
10025       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10026         /* [HGM] in protocol 1 we have to assume all variants valid */
10027         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10028         DisplayFatalError(buf, 0, 1);
10029         return;
10030       }
10031
10032       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10033         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10034                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10035            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10036            if(StrStr(cps->variants, b) == NULL) {
10037                // specific sized variant not known, check if general sizing allowed
10038                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10039                    if(StrStr(cps->variants, "boardsize") == NULL) {
10040                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10041                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10042                        DisplayFatalError(buf, 0, 1);
10043                        return;
10044                    }
10045                    /* [HGM] here we really should compare with the maximum supported board size */
10046                }
10047            }
10048       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10049       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10050       SendToProgram(buf, cps);
10051     }
10052     currentlyInitializedVariant = gameInfo.variant;
10053
10054     /* [HGM] send opening position in FRC to first engine */
10055     if(setup) {
10056           SendToProgram("force\n", cps);
10057           SendBoard(cps, 0);
10058           /* engine is now in force mode! Set flag to wake it up after first move. */
10059           setboardSpoiledMachineBlack = 1;
10060     }
10061
10062     if (cps->sendICS) {
10063       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10064       SendToProgram(buf, cps);
10065     }
10066     cps->maybeThinking = FALSE;
10067     cps->offeredDraw = 0;
10068     if (!appData.icsActive) {
10069         SendTimeControl(cps, movesPerSession, timeControl,
10070                         timeIncrement, appData.searchDepth,
10071                         searchTime);
10072     }
10073     if (appData.showThinking
10074         // [HGM] thinking: four options require thinking output to be sent
10075         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10076                                 ) {
10077         SendToProgram("post\n", cps);
10078     }
10079     SendToProgram("hard\n", cps);
10080     if (!appData.ponderNextMove) {
10081         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10082            it without being sure what state we are in first.  "hard"
10083            is not a toggle, so that one is OK.
10084          */
10085         SendToProgram("easy\n", cps);
10086     }
10087     if (cps->usePing) {
10088       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10089       SendToProgram(buf, cps);
10090     }
10091     cps->initDone = TRUE;
10092     ClearEngineOutputPane(cps == &second);
10093 }
10094
10095
10096 void
10097 ResendOptions (ChessProgramState *cps)
10098 { // send the stored value of the options
10099   int i;
10100   char buf[MSG_SIZ];
10101   Option *opt = cps->option;
10102   for(i=0; i<cps->nrOptions; i++, opt++) {
10103       switch(opt->type) {
10104         case Spin:
10105         case Slider:
10106         case CheckBox:
10107             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10108           break;
10109         case ComboBox:
10110           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10111           break;
10112         default:
10113             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10114           break;
10115         case Button:
10116         case SaveButton:
10117           continue;
10118       }
10119       SendToProgram(buf, cps);
10120   }
10121 }
10122
10123 void
10124 StartChessProgram (ChessProgramState *cps)
10125 {
10126     char buf[MSG_SIZ];
10127     int err;
10128
10129     if (appData.noChessProgram) return;
10130     cps->initDone = FALSE;
10131
10132     if (strcmp(cps->host, "localhost") == 0) {
10133         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10134     } else if (*appData.remoteShell == NULLCHAR) {
10135         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10136     } else {
10137         if (*appData.remoteUser == NULLCHAR) {
10138           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10139                     cps->program);
10140         } else {
10141           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10142                     cps->host, appData.remoteUser, cps->program);
10143         }
10144         err = StartChildProcess(buf, "", &cps->pr);
10145     }
10146
10147     if (err != 0) {
10148       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10149         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10150         if(cps != &first) return;
10151         appData.noChessProgram = TRUE;
10152         ThawUI();
10153         SetNCPMode();
10154 //      DisplayFatalError(buf, err, 1);
10155 //      cps->pr = NoProc;
10156 //      cps->isr = NULL;
10157         return;
10158     }
10159
10160     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10161     if (cps->protocolVersion > 1) {
10162       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10163       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10164         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10165         cps->comboCnt = 0;  //                and values of combo boxes
10166       }
10167       SendToProgram(buf, cps);
10168       if(cps->reload) ResendOptions(cps);
10169     } else {
10170       SendToProgram("xboard\n", cps);
10171     }
10172 }
10173
10174 void
10175 TwoMachinesEventIfReady P((void))
10176 {
10177   static int curMess = 0;
10178   if (first.lastPing != first.lastPong) {
10179     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10180     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10181     return;
10182   }
10183   if (second.lastPing != second.lastPong) {
10184     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10185     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10186     return;
10187   }
10188   DisplayMessage("", ""); curMess = 0;
10189   TwoMachinesEvent();
10190 }
10191
10192 char *
10193 MakeName (char *template)
10194 {
10195     time_t clock;
10196     struct tm *tm;
10197     static char buf[MSG_SIZ];
10198     char *p = buf;
10199     int i;
10200
10201     clock = time((time_t *)NULL);
10202     tm = localtime(&clock);
10203
10204     while(*p++ = *template++) if(p[-1] == '%') {
10205         switch(*template++) {
10206           case 0:   *p = 0; return buf;
10207           case 'Y': i = tm->tm_year+1900; break;
10208           case 'y': i = tm->tm_year-100; break;
10209           case 'M': i = tm->tm_mon+1; break;
10210           case 'd': i = tm->tm_mday; break;
10211           case 'h': i = tm->tm_hour; break;
10212           case 'm': i = tm->tm_min; break;
10213           case 's': i = tm->tm_sec; break;
10214           default:  i = 0;
10215         }
10216         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10217     }
10218     return buf;
10219 }
10220
10221 int
10222 CountPlayers (char *p)
10223 {
10224     int n = 0;
10225     while(p = strchr(p, '\n')) p++, n++; // count participants
10226     return n;
10227 }
10228
10229 FILE *
10230 WriteTourneyFile (char *results, FILE *f)
10231 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10232     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10233     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10234         // create a file with tournament description
10235         fprintf(f, "-participants {%s}\n", appData.participants);
10236         fprintf(f, "-seedBase %d\n", appData.seedBase);
10237         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10238         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10239         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10240         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10241         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10242         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10243         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10244         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10245         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10246         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10247         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10248         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10249         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10250         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10251         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10252         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10253         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10254         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10255         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10256         fprintf(f, "-smpCores %d\n", appData.smpCores);
10257         if(searchTime > 0)
10258                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10259         else {
10260                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10261                 fprintf(f, "-tc %s\n", appData.timeControl);
10262                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10263         }
10264         fprintf(f, "-results \"%s\"\n", results);
10265     }
10266     return f;
10267 }
10268
10269 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10270
10271 void
10272 Substitute (char *participants, int expunge)
10273 {
10274     int i, changed, changes=0, nPlayers=0;
10275     char *p, *q, *r, buf[MSG_SIZ];
10276     if(participants == NULL) return;
10277     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10278     r = p = participants; q = appData.participants;
10279     while(*p && *p == *q) {
10280         if(*p == '\n') r = p+1, nPlayers++;
10281         p++; q++;
10282     }
10283     if(*p) { // difference
10284         while(*p && *p++ != '\n');
10285         while(*q && *q++ != '\n');
10286       changed = nPlayers;
10287         changes = 1 + (strcmp(p, q) != 0);
10288     }
10289     if(changes == 1) { // a single engine mnemonic was changed
10290         q = r; while(*q) nPlayers += (*q++ == '\n');
10291         p = buf; while(*r && (*p = *r++) != '\n') p++;
10292         *p = NULLCHAR;
10293         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10294         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10295         if(mnemonic[i]) { // The substitute is valid
10296             FILE *f;
10297             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10298                 flock(fileno(f), LOCK_EX);
10299                 ParseArgsFromFile(f);
10300                 fseek(f, 0, SEEK_SET);
10301                 FREE(appData.participants); appData.participants = participants;
10302                 if(expunge) { // erase results of replaced engine
10303                     int len = strlen(appData.results), w, b, dummy;
10304                     for(i=0; i<len; i++) {
10305                         Pairing(i, nPlayers, &w, &b, &dummy);
10306                         if((w == changed || b == changed) && appData.results[i] == '*') {
10307                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10308                             fclose(f);
10309                             return;
10310                         }
10311                     }
10312                     for(i=0; i<len; i++) {
10313                         Pairing(i, nPlayers, &w, &b, &dummy);
10314                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10315                     }
10316                 }
10317                 WriteTourneyFile(appData.results, f);
10318                 fclose(f); // release lock
10319                 return;
10320             }
10321         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10322     }
10323     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10324     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10325     free(participants);
10326     return;
10327 }
10328
10329 int
10330 CheckPlayers (char *participants)
10331 {
10332         int i;
10333         char buf[MSG_SIZ], *p;
10334         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10335         while(p = strchr(participants, '\n')) {
10336             *p = NULLCHAR;
10337             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10338             if(!mnemonic[i]) {
10339                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10340                 *p = '\n';
10341                 DisplayError(buf, 0);
10342                 return 1;
10343             }
10344             *p = '\n';
10345             participants = p + 1;
10346         }
10347         return 0;
10348 }
10349
10350 int
10351 CreateTourney (char *name)
10352 {
10353         FILE *f;
10354         if(matchMode && strcmp(name, appData.tourneyFile)) {
10355              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10356         }
10357         if(name[0] == NULLCHAR) {
10358             if(appData.participants[0])
10359                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10360             return 0;
10361         }
10362         f = fopen(name, "r");
10363         if(f) { // file exists
10364             ASSIGN(appData.tourneyFile, name);
10365             ParseArgsFromFile(f); // parse it
10366         } else {
10367             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10368             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10369                 DisplayError(_("Not enough participants"), 0);
10370                 return 0;
10371             }
10372             if(CheckPlayers(appData.participants)) return 0;
10373             ASSIGN(appData.tourneyFile, name);
10374             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10375             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10376         }
10377         fclose(f);
10378         appData.noChessProgram = FALSE;
10379         appData.clockMode = TRUE;
10380         SetGNUMode();
10381         return 1;
10382 }
10383
10384 int
10385 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10386 {
10387     char buf[MSG_SIZ], *p, *q;
10388     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10389     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10390     skip = !all && group[0]; // if group requested, we start in skip mode
10391     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10392         p = names; q = buf; header = 0;
10393         while(*p && *p != '\n') *q++ = *p++;
10394         *q = 0;
10395         if(*p == '\n') p++;
10396         if(buf[0] == '#') {
10397             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10398             depth++; // we must be entering a new group
10399             if(all) continue; // suppress printing group headers when complete list requested
10400             header = 1;
10401             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10402         }
10403         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10404         if(engineList[i]) free(engineList[i]);
10405         engineList[i] = strdup(buf);
10406         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10407         if(engineMnemonic[i]) free(engineMnemonic[i]);
10408         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10409             strcat(buf, " (");
10410             sscanf(q + 8, "%s", buf + strlen(buf));
10411             strcat(buf, ")");
10412         }
10413         engineMnemonic[i] = strdup(buf);
10414         i++;
10415     }
10416     engineList[i] = engineMnemonic[i] = NULL;
10417     return i;
10418 }
10419
10420 // following implemented as macro to avoid type limitations
10421 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10422
10423 void
10424 SwapEngines (int n)
10425 {   // swap settings for first engine and other engine (so far only some selected options)
10426     int h;
10427     char *p;
10428     if(n == 0) return;
10429     SWAP(directory, p)
10430     SWAP(chessProgram, p)
10431     SWAP(isUCI, h)
10432     SWAP(hasOwnBookUCI, h)
10433     SWAP(protocolVersion, h)
10434     SWAP(reuse, h)
10435     SWAP(scoreIsAbsolute, h)
10436     SWAP(timeOdds, h)
10437     SWAP(logo, p)
10438     SWAP(pgnName, p)
10439     SWAP(pvSAN, h)
10440     SWAP(engOptions, p)
10441     SWAP(engInitString, p)
10442     SWAP(computerString, p)
10443     SWAP(features, p)
10444     SWAP(fenOverride, p)
10445     SWAP(NPS, h)
10446     SWAP(accumulateTC, h)
10447     SWAP(host, p)
10448 }
10449
10450 int
10451 GetEngineLine (char *s, int n)
10452 {
10453     int i;
10454     char buf[MSG_SIZ];
10455     extern char *icsNames;
10456     if(!s || !*s) return 0;
10457     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10458     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10459     if(!mnemonic[i]) return 0;
10460     if(n == 11) return 1; // just testing if there was a match
10461     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10462     if(n == 1) SwapEngines(n);
10463     ParseArgsFromString(buf);
10464     if(n == 1) SwapEngines(n);
10465     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10466         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10467         ParseArgsFromString(buf);
10468     }
10469     return 1;
10470 }
10471
10472 int
10473 SetPlayer (int player, char *p)
10474 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10475     int i;
10476     char buf[MSG_SIZ], *engineName;
10477     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10478     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10479     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10480     if(mnemonic[i]) {
10481         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10482         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10483         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10484         ParseArgsFromString(buf);
10485     } else { // no engine with this nickname is installed!
10486         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10487         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10488         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10489         ModeHighlight();
10490         DisplayError(buf, 0);
10491         return 0;
10492     }
10493     free(engineName);
10494     return i;
10495 }
10496
10497 char *recentEngines;
10498
10499 void
10500 RecentEngineEvent (int nr)
10501 {
10502     int n;
10503 //    SwapEngines(1); // bump first to second
10504 //    ReplaceEngine(&second, 1); // and load it there
10505     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10506     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10507     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10508         ReplaceEngine(&first, 0);
10509         FloatToFront(&appData.recentEngineList, command[n]);
10510     }
10511 }
10512
10513 int
10514 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10515 {   // determine players from game number
10516     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10517
10518     if(appData.tourneyType == 0) {
10519         roundsPerCycle = (nPlayers - 1) | 1;
10520         pairingsPerRound = nPlayers / 2;
10521     } else if(appData.tourneyType > 0) {
10522         roundsPerCycle = nPlayers - appData.tourneyType;
10523         pairingsPerRound = appData.tourneyType;
10524     }
10525     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10526     gamesPerCycle = gamesPerRound * roundsPerCycle;
10527     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10528     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10529     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10530     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10531     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10532     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10533
10534     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10535     if(appData.roundSync) *syncInterval = gamesPerRound;
10536
10537     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10538
10539     if(appData.tourneyType == 0) {
10540         if(curPairing == (nPlayers-1)/2 ) {
10541             *whitePlayer = curRound;
10542             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10543         } else {
10544             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10545             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10546             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10547             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10548         }
10549     } else if(appData.tourneyType > 1) {
10550         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10551         *whitePlayer = curRound + appData.tourneyType;
10552     } else if(appData.tourneyType > 0) {
10553         *whitePlayer = curPairing;
10554         *blackPlayer = curRound + appData.tourneyType;
10555     }
10556
10557     // take care of white/black alternation per round.
10558     // For cycles and games this is already taken care of by default, derived from matchGame!
10559     return curRound & 1;
10560 }
10561
10562 int
10563 NextTourneyGame (int nr, int *swapColors)
10564 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10565     char *p, *q;
10566     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10567     FILE *tf;
10568     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10569     tf = fopen(appData.tourneyFile, "r");
10570     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10571     ParseArgsFromFile(tf); fclose(tf);
10572     InitTimeControls(); // TC might be altered from tourney file
10573
10574     nPlayers = CountPlayers(appData.participants); // count participants
10575     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10576     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10577
10578     if(syncInterval) {
10579         p = q = appData.results;
10580         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10581         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10582             DisplayMessage(_("Waiting for other game(s)"),"");
10583             waitingForGame = TRUE;
10584             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10585             return 0;
10586         }
10587         waitingForGame = FALSE;
10588     }
10589
10590     if(appData.tourneyType < 0) {
10591         if(nr>=0 && !pairingReceived) {
10592             char buf[1<<16];
10593             if(pairing.pr == NoProc) {
10594                 if(!appData.pairingEngine[0]) {
10595                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10596                     return 0;
10597                 }
10598                 StartChessProgram(&pairing); // starts the pairing engine
10599             }
10600             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10601             SendToProgram(buf, &pairing);
10602             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10603             SendToProgram(buf, &pairing);
10604             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10605         }
10606         pairingReceived = 0;                              // ... so we continue here
10607         *swapColors = 0;
10608         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10609         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10610         matchGame = 1; roundNr = nr / syncInterval + 1;
10611     }
10612
10613     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10614
10615     // redefine engines, engine dir, etc.
10616     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10617     if(first.pr == NoProc) {
10618       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10619       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10620     }
10621     if(second.pr == NoProc) {
10622       SwapEngines(1);
10623       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10624       SwapEngines(1);         // and make that valid for second engine by swapping
10625       InitEngine(&second, 1);
10626     }
10627     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10628     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10629     return OK;
10630 }
10631
10632 void
10633 NextMatchGame ()
10634 {   // performs game initialization that does not invoke engines, and then tries to start the game
10635     int res, firstWhite, swapColors = 0;
10636     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10637     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
10638         char buf[MSG_SIZ];
10639         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10640         if(strcmp(buf, currentDebugFile)) { // name has changed
10641             FILE *f = fopen(buf, "w");
10642             if(f) { // if opening the new file failed, just keep using the old one
10643                 ASSIGN(currentDebugFile, buf);
10644                 fclose(debugFP);
10645                 debugFP = f;
10646             }
10647             if(appData.serverFileName) {
10648                 if(serverFP) fclose(serverFP);
10649                 serverFP = fopen(appData.serverFileName, "w");
10650                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10651                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10652             }
10653         }
10654     }
10655     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10656     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10657     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10658     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10659     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10660     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10661     Reset(FALSE, first.pr != NoProc);
10662     res = LoadGameOrPosition(matchGame); // setup game
10663     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10664     if(!res) return; // abort when bad game/pos file
10665     TwoMachinesEvent();
10666 }
10667
10668 void
10669 UserAdjudicationEvent (int result)
10670 {
10671     ChessMove gameResult = GameIsDrawn;
10672
10673     if( result > 0 ) {
10674         gameResult = WhiteWins;
10675     }
10676     else if( result < 0 ) {
10677         gameResult = BlackWins;
10678     }
10679
10680     if( gameMode == TwoMachinesPlay ) {
10681         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10682     }
10683 }
10684
10685
10686 // [HGM] save: calculate checksum of game to make games easily identifiable
10687 int
10688 StringCheckSum (char *s)
10689 {
10690         int i = 0;
10691         if(s==NULL) return 0;
10692         while(*s) i = i*259 + *s++;
10693         return i;
10694 }
10695
10696 int
10697 GameCheckSum ()
10698 {
10699         int i, sum=0;
10700         for(i=backwardMostMove; i<forwardMostMove; i++) {
10701                 sum += pvInfoList[i].depth;
10702                 sum += StringCheckSum(parseList[i]);
10703                 sum += StringCheckSum(commentList[i]);
10704                 sum *= 261;
10705         }
10706         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10707         return sum + StringCheckSum(commentList[i]);
10708 } // end of save patch
10709
10710 void
10711 GameEnds (ChessMove result, char *resultDetails, int whosays)
10712 {
10713     GameMode nextGameMode;
10714     int isIcsGame;
10715     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10716
10717     if(endingGame) return; /* [HGM] crash: forbid recursion */
10718     endingGame = 1;
10719     if(twoBoards) { // [HGM] dual: switch back to one board
10720         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10721         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10722     }
10723     if (appData.debugMode) {
10724       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10725               result, resultDetails ? resultDetails : "(null)", whosays);
10726     }
10727
10728     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10729
10730     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10731
10732     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10733         /* If we are playing on ICS, the server decides when the
10734            game is over, but the engine can offer to draw, claim
10735            a draw, or resign.
10736          */
10737 #if ZIPPY
10738         if (appData.zippyPlay && first.initDone) {
10739             if (result == GameIsDrawn) {
10740                 /* In case draw still needs to be claimed */
10741                 SendToICS(ics_prefix);
10742                 SendToICS("draw\n");
10743             } else if (StrCaseStr(resultDetails, "resign")) {
10744                 SendToICS(ics_prefix);
10745                 SendToICS("resign\n");
10746             }
10747         }
10748 #endif
10749         endingGame = 0; /* [HGM] crash */
10750         return;
10751     }
10752
10753     /* If we're loading the game from a file, stop */
10754     if (whosays == GE_FILE) {
10755       (void) StopLoadGameTimer();
10756       gameFileFP = NULL;
10757     }
10758
10759     /* Cancel draw offers */
10760     first.offeredDraw = second.offeredDraw = 0;
10761
10762     /* If this is an ICS game, only ICS can really say it's done;
10763        if not, anyone can. */
10764     isIcsGame = (gameMode == IcsPlayingWhite ||
10765                  gameMode == IcsPlayingBlack ||
10766                  gameMode == IcsObserving    ||
10767                  gameMode == IcsExamining);
10768
10769     if (!isIcsGame || whosays == GE_ICS) {
10770         /* OK -- not an ICS game, or ICS said it was done */
10771         StopClocks();
10772         if (!isIcsGame && !appData.noChessProgram)
10773           SetUserThinkingEnables();
10774
10775         /* [HGM] if a machine claims the game end we verify this claim */
10776         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10777             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10778                 char claimer;
10779                 ChessMove trueResult = (ChessMove) -1;
10780
10781                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10782                                             first.twoMachinesColor[0] :
10783                                             second.twoMachinesColor[0] ;
10784
10785                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10786                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10787                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10788                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10789                 } else
10790                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10791                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10792                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10793                 } else
10794                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10795                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10796                 }
10797
10798                 // now verify win claims, but not in drop games, as we don't understand those yet
10799                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10800                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10801                     (result == WhiteWins && claimer == 'w' ||
10802                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10803                       if (appData.debugMode) {
10804                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10805                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10806                       }
10807                       if(result != trueResult) {
10808                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10809                               result = claimer == 'w' ? BlackWins : WhiteWins;
10810                               resultDetails = buf;
10811                       }
10812                 } else
10813                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10814                     && (forwardMostMove <= backwardMostMove ||
10815                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10816                         (claimer=='b')==(forwardMostMove&1))
10817                                                                                   ) {
10818                       /* [HGM] verify: draws that were not flagged are false claims */
10819                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10820                       result = claimer == 'w' ? BlackWins : WhiteWins;
10821                       resultDetails = buf;
10822                 }
10823                 /* (Claiming a loss is accepted no questions asked!) */
10824             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10825                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10826                 result = GameUnfinished;
10827                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10828             }
10829             /* [HGM] bare: don't allow bare King to win */
10830             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10831                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10832                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10833                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10834                && result != GameIsDrawn)
10835             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10836                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10837                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10838                         if(p >= 0 && p <= (int)WhiteKing) k++;
10839                 }
10840                 if (appData.debugMode) {
10841                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10842                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10843                 }
10844                 if(k <= 1) {
10845                         result = GameIsDrawn;
10846                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10847                         resultDetails = buf;
10848                 }
10849             }
10850         }
10851
10852
10853         if(serverMoves != NULL && !loadFlag) { char c = '=';
10854             if(result==WhiteWins) c = '+';
10855             if(result==BlackWins) c = '-';
10856             if(resultDetails != NULL)
10857                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10858         }
10859         if (resultDetails != NULL) {
10860             gameInfo.result = result;
10861             gameInfo.resultDetails = StrSave(resultDetails);
10862
10863             /* display last move only if game was not loaded from file */
10864             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10865                 DisplayMove(currentMove - 1);
10866
10867             if (forwardMostMove != 0) {
10868                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10869                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10870                                                                 ) {
10871                     if (*appData.saveGameFile != NULLCHAR) {
10872                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10873                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10874                         else
10875                         SaveGameToFile(appData.saveGameFile, TRUE);
10876                     } else if (appData.autoSaveGames) {
10877                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10878                     }
10879                     if (*appData.savePositionFile != NULLCHAR) {
10880                         SavePositionToFile(appData.savePositionFile);
10881                     }
10882                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10883                 }
10884             }
10885
10886             /* Tell program how game ended in case it is learning */
10887             /* [HGM] Moved this to after saving the PGN, just in case */
10888             /* engine died and we got here through time loss. In that */
10889             /* case we will get a fatal error writing the pipe, which */
10890             /* would otherwise lose us the PGN.                       */
10891             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10892             /* output during GameEnds should never be fatal anymore   */
10893             if (gameMode == MachinePlaysWhite ||
10894                 gameMode == MachinePlaysBlack ||
10895                 gameMode == TwoMachinesPlay ||
10896                 gameMode == IcsPlayingWhite ||
10897                 gameMode == IcsPlayingBlack ||
10898                 gameMode == BeginningOfGame) {
10899                 char buf[MSG_SIZ];
10900                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10901                         resultDetails);
10902                 if (first.pr != NoProc) {
10903                     SendToProgram(buf, &first);
10904                 }
10905                 if (second.pr != NoProc &&
10906                     gameMode == TwoMachinesPlay) {
10907                     SendToProgram(buf, &second);
10908                 }
10909             }
10910         }
10911
10912         if (appData.icsActive) {
10913             if (appData.quietPlay &&
10914                 (gameMode == IcsPlayingWhite ||
10915                  gameMode == IcsPlayingBlack)) {
10916                 SendToICS(ics_prefix);
10917                 SendToICS("set shout 1\n");
10918             }
10919             nextGameMode = IcsIdle;
10920             ics_user_moved = FALSE;
10921             /* clean up premove.  It's ugly when the game has ended and the
10922              * premove highlights are still on the board.
10923              */
10924             if (gotPremove) {
10925               gotPremove = FALSE;
10926               ClearPremoveHighlights();
10927               DrawPosition(FALSE, boards[currentMove]);
10928             }
10929             if (whosays == GE_ICS) {
10930                 switch (result) {
10931                 case WhiteWins:
10932                     if (gameMode == IcsPlayingWhite)
10933                         PlayIcsWinSound();
10934                     else if(gameMode == IcsPlayingBlack)
10935                         PlayIcsLossSound();
10936                     break;
10937                 case BlackWins:
10938                     if (gameMode == IcsPlayingBlack)
10939                         PlayIcsWinSound();
10940                     else if(gameMode == IcsPlayingWhite)
10941                         PlayIcsLossSound();
10942                     break;
10943                 case GameIsDrawn:
10944                     PlayIcsDrawSound();
10945                     break;
10946                 default:
10947                     PlayIcsUnfinishedSound();
10948                 }
10949             }
10950             if(appData.quitNext) { ExitEvent(0); return; }
10951         } else if (gameMode == EditGame ||
10952                    gameMode == PlayFromGameFile ||
10953                    gameMode == AnalyzeMode ||
10954                    gameMode == AnalyzeFile) {
10955             nextGameMode = gameMode;
10956         } else {
10957             nextGameMode = EndOfGame;
10958         }
10959         pausing = FALSE;
10960         ModeHighlight();
10961     } else {
10962         nextGameMode = gameMode;
10963     }
10964
10965     if (appData.noChessProgram) {
10966         gameMode = nextGameMode;
10967         ModeHighlight();
10968         endingGame = 0; /* [HGM] crash */
10969         return;
10970     }
10971
10972     if (first.reuse) {
10973         /* Put first chess program into idle state */
10974         if (first.pr != NoProc &&
10975             (gameMode == MachinePlaysWhite ||
10976              gameMode == MachinePlaysBlack ||
10977              gameMode == TwoMachinesPlay ||
10978              gameMode == IcsPlayingWhite ||
10979              gameMode == IcsPlayingBlack ||
10980              gameMode == BeginningOfGame)) {
10981             SendToProgram("force\n", &first);
10982             if (first.usePing) {
10983               char buf[MSG_SIZ];
10984               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10985               SendToProgram(buf, &first);
10986             }
10987         }
10988     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10989         /* Kill off first chess program */
10990         if (first.isr != NULL)
10991           RemoveInputSource(first.isr);
10992         first.isr = NULL;
10993
10994         if (first.pr != NoProc) {
10995             ExitAnalyzeMode();
10996             DoSleep( appData.delayBeforeQuit );
10997             SendToProgram("quit\n", &first);
10998             DoSleep( appData.delayAfterQuit );
10999             DestroyChildProcess(first.pr, first.useSigterm);
11000             first.reload = TRUE;
11001         }
11002         first.pr = NoProc;
11003     }
11004     if (second.reuse) {
11005         /* Put second chess program into idle state */
11006         if (second.pr != NoProc &&
11007             gameMode == TwoMachinesPlay) {
11008             SendToProgram("force\n", &second);
11009             if (second.usePing) {
11010               char buf[MSG_SIZ];
11011               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11012               SendToProgram(buf, &second);
11013             }
11014         }
11015     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11016         /* Kill off second chess program */
11017         if (second.isr != NULL)
11018           RemoveInputSource(second.isr);
11019         second.isr = NULL;
11020
11021         if (second.pr != NoProc) {
11022             DoSleep( appData.delayBeforeQuit );
11023             SendToProgram("quit\n", &second);
11024             DoSleep( appData.delayAfterQuit );
11025             DestroyChildProcess(second.pr, second.useSigterm);
11026             second.reload = TRUE;
11027         }
11028         second.pr = NoProc;
11029     }
11030
11031     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11032         char resChar = '=';
11033         switch (result) {
11034         case WhiteWins:
11035           resChar = '+';
11036           if (first.twoMachinesColor[0] == 'w') {
11037             first.matchWins++;
11038           } else {
11039             second.matchWins++;
11040           }
11041           break;
11042         case BlackWins:
11043           resChar = '-';
11044           if (first.twoMachinesColor[0] == 'b') {
11045             first.matchWins++;
11046           } else {
11047             second.matchWins++;
11048           }
11049           break;
11050         case GameUnfinished:
11051           resChar = ' ';
11052         default:
11053           break;
11054         }
11055
11056         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11057         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11058             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11059             ReserveGame(nextGame, resChar); // sets nextGame
11060             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11061             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11062         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11063
11064         if (nextGame <= appData.matchGames && !abortMatch) {
11065             gameMode = nextGameMode;
11066             matchGame = nextGame; // this will be overruled in tourney mode!
11067             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11068             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11069             endingGame = 0; /* [HGM] crash */
11070             return;
11071         } else {
11072             gameMode = nextGameMode;
11073             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11074                      first.tidy, second.tidy,
11075                      first.matchWins, second.matchWins,
11076                      appData.matchGames - (first.matchWins + second.matchWins));
11077             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11078             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11079             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11080             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11081                 first.twoMachinesColor = "black\n";
11082                 second.twoMachinesColor = "white\n";
11083             } else {
11084                 first.twoMachinesColor = "white\n";
11085                 second.twoMachinesColor = "black\n";
11086             }
11087         }
11088     }
11089     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11090         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11091       ExitAnalyzeMode();
11092     gameMode = nextGameMode;
11093     ModeHighlight();
11094     endingGame = 0;  /* [HGM] crash */
11095     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11096         if(matchMode == TRUE) { // match through command line: exit with or without popup
11097             if(ranking) {
11098                 ToNrEvent(forwardMostMove);
11099                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11100                 else ExitEvent(0);
11101             } else DisplayFatalError(buf, 0, 0);
11102         } else { // match through menu; just stop, with or without popup
11103             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11104             ModeHighlight();
11105             if(ranking){
11106                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11107             } else DisplayNote(buf);
11108       }
11109       if(ranking) free(ranking);
11110     }
11111 }
11112
11113 /* Assumes program was just initialized (initString sent).
11114    Leaves program in force mode. */
11115 void
11116 FeedMovesToProgram (ChessProgramState *cps, int upto)
11117 {
11118     int i;
11119
11120     if (appData.debugMode)
11121       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11122               startedFromSetupPosition ? "position and " : "",
11123               backwardMostMove, upto, cps->which);
11124     if(currentlyInitializedVariant != gameInfo.variant) {
11125       char buf[MSG_SIZ];
11126         // [HGM] variantswitch: make engine aware of new variant
11127         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11128                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11129         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11130         SendToProgram(buf, cps);
11131         currentlyInitializedVariant = gameInfo.variant;
11132     }
11133     SendToProgram("force\n", cps);
11134     if (startedFromSetupPosition) {
11135         SendBoard(cps, backwardMostMove);
11136     if (appData.debugMode) {
11137         fprintf(debugFP, "feedMoves\n");
11138     }
11139     }
11140     for (i = backwardMostMove; i < upto; i++) {
11141         SendMoveToProgram(i, cps);
11142     }
11143 }
11144
11145
11146 int
11147 ResurrectChessProgram ()
11148 {
11149      /* The chess program may have exited.
11150         If so, restart it and feed it all the moves made so far. */
11151     static int doInit = 0;
11152
11153     if (appData.noChessProgram) return 1;
11154
11155     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11156         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11157         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11158         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11159     } else {
11160         if (first.pr != NoProc) return 1;
11161         StartChessProgram(&first);
11162     }
11163     InitChessProgram(&first, FALSE);
11164     FeedMovesToProgram(&first, currentMove);
11165
11166     if (!first.sendTime) {
11167         /* can't tell gnuchess what its clock should read,
11168            so we bow to its notion. */
11169         ResetClocks();
11170         timeRemaining[0][currentMove] = whiteTimeRemaining;
11171         timeRemaining[1][currentMove] = blackTimeRemaining;
11172     }
11173
11174     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11175                 appData.icsEngineAnalyze) && first.analysisSupport) {
11176       SendToProgram("analyze\n", &first);
11177       first.analyzing = TRUE;
11178     }
11179     return 1;
11180 }
11181
11182 /*
11183  * Button procedures
11184  */
11185 void
11186 Reset (int redraw, int init)
11187 {
11188     int i;
11189
11190     if (appData.debugMode) {
11191         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11192                 redraw, init, gameMode);
11193     }
11194     CleanupTail(); // [HGM] vari: delete any stored variations
11195     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11196     pausing = pauseExamInvalid = FALSE;
11197     startedFromSetupPosition = blackPlaysFirst = FALSE;
11198     firstMove = TRUE;
11199     whiteFlag = blackFlag = FALSE;
11200     userOfferedDraw = FALSE;
11201     hintRequested = bookRequested = FALSE;
11202     first.maybeThinking = FALSE;
11203     second.maybeThinking = FALSE;
11204     first.bookSuspend = FALSE; // [HGM] book
11205     second.bookSuspend = FALSE;
11206     thinkOutput[0] = NULLCHAR;
11207     lastHint[0] = NULLCHAR;
11208     ClearGameInfo(&gameInfo);
11209     gameInfo.variant = StringToVariant(appData.variant);
11210     ics_user_moved = ics_clock_paused = FALSE;
11211     ics_getting_history = H_FALSE;
11212     ics_gamenum = -1;
11213     white_holding[0] = black_holding[0] = NULLCHAR;
11214     ClearProgramStats();
11215     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11216
11217     ResetFrontEnd();
11218     ClearHighlights();
11219     flipView = appData.flipView;
11220     ClearPremoveHighlights();
11221     gotPremove = FALSE;
11222     alarmSounded = FALSE;
11223
11224     GameEnds(EndOfFile, NULL, GE_PLAYER);
11225     if(appData.serverMovesName != NULL) {
11226         /* [HGM] prepare to make moves file for broadcasting */
11227         clock_t t = clock();
11228         if(serverMoves != NULL) fclose(serverMoves);
11229         serverMoves = fopen(appData.serverMovesName, "r");
11230         if(serverMoves != NULL) {
11231             fclose(serverMoves);
11232             /* delay 15 sec before overwriting, so all clients can see end */
11233             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11234         }
11235         serverMoves = fopen(appData.serverMovesName, "w");
11236     }
11237
11238     ExitAnalyzeMode();
11239     gameMode = BeginningOfGame;
11240     ModeHighlight();
11241     if(appData.icsActive) gameInfo.variant = VariantNormal;
11242     currentMove = forwardMostMove = backwardMostMove = 0;
11243     MarkTargetSquares(1);
11244     InitPosition(redraw);
11245     for (i = 0; i < MAX_MOVES; i++) {
11246         if (commentList[i] != NULL) {
11247             free(commentList[i]);
11248             commentList[i] = NULL;
11249         }
11250     }
11251     ResetClocks();
11252     timeRemaining[0][0] = whiteTimeRemaining;
11253     timeRemaining[1][0] = blackTimeRemaining;
11254
11255     if (first.pr == NoProc) {
11256         StartChessProgram(&first);
11257     }
11258     if (init) {
11259             InitChessProgram(&first, startedFromSetupPosition);
11260     }
11261     DisplayTitle("");
11262     DisplayMessage("", "");
11263     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11264     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11265     ClearMap();        // [HGM] exclude: invalidate map
11266 }
11267
11268 void
11269 AutoPlayGameLoop ()
11270 {
11271     for (;;) {
11272         if (!AutoPlayOneMove())
11273           return;
11274         if (matchMode || appData.timeDelay == 0)
11275           continue;
11276         if (appData.timeDelay < 0)
11277           return;
11278         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11279         break;
11280     }
11281 }
11282
11283 void
11284 AnalyzeNextGame()
11285 {
11286     ReloadGame(1); // next game
11287 }
11288
11289 int
11290 AutoPlayOneMove ()
11291 {
11292     int fromX, fromY, toX, toY;
11293
11294     if (appData.debugMode) {
11295       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11296     }
11297
11298     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11299       return FALSE;
11300
11301     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11302       pvInfoList[currentMove].depth = programStats.depth;
11303       pvInfoList[currentMove].score = programStats.score;
11304       pvInfoList[currentMove].time  = 0;
11305       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11306       else { // append analysis of final position as comment
11307         char buf[MSG_SIZ];
11308         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11309         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11310       }
11311       programStats.depth = 0;
11312     }
11313
11314     if (currentMove >= forwardMostMove) {
11315       if(gameMode == AnalyzeFile) {
11316           if(appData.loadGameIndex == -1) {
11317             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11318           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11319           } else {
11320           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11321         }
11322       }
11323 //      gameMode = EndOfGame;
11324 //      ModeHighlight();
11325
11326       /* [AS] Clear current move marker at the end of a game */
11327       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11328
11329       return FALSE;
11330     }
11331
11332     toX = moveList[currentMove][2] - AAA;
11333     toY = moveList[currentMove][3] - ONE;
11334
11335     if (moveList[currentMove][1] == '@') {
11336         if (appData.highlightLastMove) {
11337             SetHighlights(-1, -1, toX, toY);
11338         }
11339     } else {
11340         fromX = moveList[currentMove][0] - AAA;
11341         fromY = moveList[currentMove][1] - ONE;
11342
11343         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11344
11345         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11346
11347         if (appData.highlightLastMove) {
11348             SetHighlights(fromX, fromY, toX, toY);
11349         }
11350     }
11351     DisplayMove(currentMove);
11352     SendMoveToProgram(currentMove++, &first);
11353     DisplayBothClocks();
11354     DrawPosition(FALSE, boards[currentMove]);
11355     // [HGM] PV info: always display, routine tests if empty
11356     DisplayComment(currentMove - 1, commentList[currentMove]);
11357     return TRUE;
11358 }
11359
11360
11361 int
11362 LoadGameOneMove (ChessMove readAhead)
11363 {
11364     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11365     char promoChar = NULLCHAR;
11366     ChessMove moveType;
11367     char move[MSG_SIZ];
11368     char *p, *q;
11369
11370     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11371         gameMode != AnalyzeMode && gameMode != Training) {
11372         gameFileFP = NULL;
11373         return FALSE;
11374     }
11375
11376     yyboardindex = forwardMostMove;
11377     if (readAhead != EndOfFile) {
11378       moveType = readAhead;
11379     } else {
11380       if (gameFileFP == NULL)
11381           return FALSE;
11382       moveType = (ChessMove) Myylex();
11383     }
11384
11385     done = FALSE;
11386     switch (moveType) {
11387       case Comment:
11388         if (appData.debugMode)
11389           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11390         p = yy_text;
11391
11392         /* append the comment but don't display it */
11393         AppendComment(currentMove, p, FALSE);
11394         return TRUE;
11395
11396       case WhiteCapturesEnPassant:
11397       case BlackCapturesEnPassant:
11398       case WhitePromotion:
11399       case BlackPromotion:
11400       case WhiteNonPromotion:
11401       case BlackNonPromotion:
11402       case NormalMove:
11403       case WhiteKingSideCastle:
11404       case WhiteQueenSideCastle:
11405       case BlackKingSideCastle:
11406       case BlackQueenSideCastle:
11407       case WhiteKingSideCastleWild:
11408       case WhiteQueenSideCastleWild:
11409       case BlackKingSideCastleWild:
11410       case BlackQueenSideCastleWild:
11411       /* PUSH Fabien */
11412       case WhiteHSideCastleFR:
11413       case WhiteASideCastleFR:
11414       case BlackHSideCastleFR:
11415       case BlackASideCastleFR:
11416       /* POP Fabien */
11417         if (appData.debugMode)
11418           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11419         fromX = currentMoveString[0] - AAA;
11420         fromY = currentMoveString[1] - ONE;
11421         toX = currentMoveString[2] - AAA;
11422         toY = currentMoveString[3] - ONE;
11423         promoChar = currentMoveString[4];
11424         break;
11425
11426       case WhiteDrop:
11427       case BlackDrop:
11428         if (appData.debugMode)
11429           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11430         fromX = moveType == WhiteDrop ?
11431           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11432         (int) CharToPiece(ToLower(currentMoveString[0]));
11433         fromY = DROP_RANK;
11434         toX = currentMoveString[2] - AAA;
11435         toY = currentMoveString[3] - ONE;
11436         break;
11437
11438       case WhiteWins:
11439       case BlackWins:
11440       case GameIsDrawn:
11441       case GameUnfinished:
11442         if (appData.debugMode)
11443           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11444         p = strchr(yy_text, '{');
11445         if (p == NULL) p = strchr(yy_text, '(');
11446         if (p == NULL) {
11447             p = yy_text;
11448             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11449         } else {
11450             q = strchr(p, *p == '{' ? '}' : ')');
11451             if (q != NULL) *q = NULLCHAR;
11452             p++;
11453         }
11454         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11455         GameEnds(moveType, p, GE_FILE);
11456         done = TRUE;
11457         if (cmailMsgLoaded) {
11458             ClearHighlights();
11459             flipView = WhiteOnMove(currentMove);
11460             if (moveType == GameUnfinished) flipView = !flipView;
11461             if (appData.debugMode)
11462               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11463         }
11464         break;
11465
11466       case EndOfFile:
11467         if (appData.debugMode)
11468           fprintf(debugFP, "Parser hit end of file\n");
11469         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11470           case MT_NONE:
11471           case MT_CHECK:
11472             break;
11473           case MT_CHECKMATE:
11474           case MT_STAINMATE:
11475             if (WhiteOnMove(currentMove)) {
11476                 GameEnds(BlackWins, "Black mates", GE_FILE);
11477             } else {
11478                 GameEnds(WhiteWins, "White mates", GE_FILE);
11479             }
11480             break;
11481           case MT_STALEMATE:
11482             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11483             break;
11484         }
11485         done = TRUE;
11486         break;
11487
11488       case MoveNumberOne:
11489         if (lastLoadGameStart == GNUChessGame) {
11490             /* GNUChessGames have numbers, but they aren't move numbers */
11491             if (appData.debugMode)
11492               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11493                       yy_text, (int) moveType);
11494             return LoadGameOneMove(EndOfFile); /* tail recursion */
11495         }
11496         /* else fall thru */
11497
11498       case XBoardGame:
11499       case GNUChessGame:
11500       case PGNTag:
11501         /* Reached start of next game in file */
11502         if (appData.debugMode)
11503           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11504         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11505           case MT_NONE:
11506           case MT_CHECK:
11507             break;
11508           case MT_CHECKMATE:
11509           case MT_STAINMATE:
11510             if (WhiteOnMove(currentMove)) {
11511                 GameEnds(BlackWins, "Black mates", GE_FILE);
11512             } else {
11513                 GameEnds(WhiteWins, "White mates", GE_FILE);
11514             }
11515             break;
11516           case MT_STALEMATE:
11517             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11518             break;
11519         }
11520         done = TRUE;
11521         break;
11522
11523       case PositionDiagram:     /* should not happen; ignore */
11524       case ElapsedTime:         /* ignore */
11525       case NAG:                 /* ignore */
11526         if (appData.debugMode)
11527           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11528                   yy_text, (int) moveType);
11529         return LoadGameOneMove(EndOfFile); /* tail recursion */
11530
11531       case IllegalMove:
11532         if (appData.testLegality) {
11533             if (appData.debugMode)
11534               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11535             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11536                     (forwardMostMove / 2) + 1,
11537                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11538             DisplayError(move, 0);
11539             done = TRUE;
11540         } else {
11541             if (appData.debugMode)
11542               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11543                       yy_text, currentMoveString);
11544             fromX = currentMoveString[0] - AAA;
11545             fromY = currentMoveString[1] - ONE;
11546             toX = currentMoveString[2] - AAA;
11547             toY = currentMoveString[3] - ONE;
11548             promoChar = currentMoveString[4];
11549         }
11550         break;
11551
11552       case AmbiguousMove:
11553         if (appData.debugMode)
11554           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11555         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11556                 (forwardMostMove / 2) + 1,
11557                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11558         DisplayError(move, 0);
11559         done = TRUE;
11560         break;
11561
11562       default:
11563       case ImpossibleMove:
11564         if (appData.debugMode)
11565           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11566         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11567                 (forwardMostMove / 2) + 1,
11568                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11569         DisplayError(move, 0);
11570         done = TRUE;
11571         break;
11572     }
11573
11574     if (done) {
11575         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11576             DrawPosition(FALSE, boards[currentMove]);
11577             DisplayBothClocks();
11578             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11579               DisplayComment(currentMove - 1, commentList[currentMove]);
11580         }
11581         (void) StopLoadGameTimer();
11582         gameFileFP = NULL;
11583         cmailOldMove = forwardMostMove;
11584         return FALSE;
11585     } else {
11586         /* currentMoveString is set as a side-effect of yylex */
11587
11588         thinkOutput[0] = NULLCHAR;
11589         MakeMove(fromX, fromY, toX, toY, promoChar);
11590         currentMove = forwardMostMove;
11591         return TRUE;
11592     }
11593 }
11594
11595 /* Load the nth game from the given file */
11596 int
11597 LoadGameFromFile (char *filename, int n, char *title, int useList)
11598 {
11599     FILE *f;
11600     char buf[MSG_SIZ];
11601
11602     if (strcmp(filename, "-") == 0) {
11603         f = stdin;
11604         title = "stdin";
11605     } else {
11606         f = fopen(filename, "rb");
11607         if (f == NULL) {
11608           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11609             DisplayError(buf, errno);
11610             return FALSE;
11611         }
11612     }
11613     if (fseek(f, 0, 0) == -1) {
11614         /* f is not seekable; probably a pipe */
11615         useList = FALSE;
11616     }
11617     if (useList && n == 0) {
11618         int error = GameListBuild(f);
11619         if (error) {
11620             DisplayError(_("Cannot build game list"), error);
11621         } else if (!ListEmpty(&gameList) &&
11622                    ((ListGame *) gameList.tailPred)->number > 1) {
11623             GameListPopUp(f, title);
11624             return TRUE;
11625         }
11626         GameListDestroy();
11627         n = 1;
11628     }
11629     if (n == 0) n = 1;
11630     return LoadGame(f, n, title, FALSE);
11631 }
11632
11633
11634 void
11635 MakeRegisteredMove ()
11636 {
11637     int fromX, fromY, toX, toY;
11638     char promoChar;
11639     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11640         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11641           case CMAIL_MOVE:
11642           case CMAIL_DRAW:
11643             if (appData.debugMode)
11644               fprintf(debugFP, "Restoring %s for game %d\n",
11645                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11646
11647             thinkOutput[0] = NULLCHAR;
11648             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11649             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11650             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11651             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11652             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11653             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11654             MakeMove(fromX, fromY, toX, toY, promoChar);
11655             ShowMove(fromX, fromY, toX, toY);
11656
11657             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11658               case MT_NONE:
11659               case MT_CHECK:
11660                 break;
11661
11662               case MT_CHECKMATE:
11663               case MT_STAINMATE:
11664                 if (WhiteOnMove(currentMove)) {
11665                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11666                 } else {
11667                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11668                 }
11669                 break;
11670
11671               case MT_STALEMATE:
11672                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11673                 break;
11674             }
11675
11676             break;
11677
11678           case CMAIL_RESIGN:
11679             if (WhiteOnMove(currentMove)) {
11680                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11681             } else {
11682                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11683             }
11684             break;
11685
11686           case CMAIL_ACCEPT:
11687             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11688             break;
11689
11690           default:
11691             break;
11692         }
11693     }
11694
11695     return;
11696 }
11697
11698 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11699 int
11700 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11701 {
11702     int retVal;
11703
11704     if (gameNumber > nCmailGames) {
11705         DisplayError(_("No more games in this message"), 0);
11706         return FALSE;
11707     }
11708     if (f == lastLoadGameFP) {
11709         int offset = gameNumber - lastLoadGameNumber;
11710         if (offset == 0) {
11711             cmailMsg[0] = NULLCHAR;
11712             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11713                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11714                 nCmailMovesRegistered--;
11715             }
11716             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11717             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11718                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11719             }
11720         } else {
11721             if (! RegisterMove()) return FALSE;
11722         }
11723     }
11724
11725     retVal = LoadGame(f, gameNumber, title, useList);
11726
11727     /* Make move registered during previous look at this game, if any */
11728     MakeRegisteredMove();
11729
11730     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11731         commentList[currentMove]
11732           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11733         DisplayComment(currentMove - 1, commentList[currentMove]);
11734     }
11735
11736     return retVal;
11737 }
11738
11739 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11740 int
11741 ReloadGame (int offset)
11742 {
11743     int gameNumber = lastLoadGameNumber + offset;
11744     if (lastLoadGameFP == NULL) {
11745         DisplayError(_("No game has been loaded yet"), 0);
11746         return FALSE;
11747     }
11748     if (gameNumber <= 0) {
11749         DisplayError(_("Can't back up any further"), 0);
11750         return FALSE;
11751     }
11752     if (cmailMsgLoaded) {
11753         return CmailLoadGame(lastLoadGameFP, gameNumber,
11754                              lastLoadGameTitle, lastLoadGameUseList);
11755     } else {
11756         return LoadGame(lastLoadGameFP, gameNumber,
11757                         lastLoadGameTitle, lastLoadGameUseList);
11758     }
11759 }
11760
11761 int keys[EmptySquare+1];
11762
11763 int
11764 PositionMatches (Board b1, Board b2)
11765 {
11766     int r, f, sum=0;
11767     switch(appData.searchMode) {
11768         case 1: return CompareWithRights(b1, b2);
11769         case 2:
11770             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11771                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11772             }
11773             return TRUE;
11774         case 3:
11775             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11776               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11777                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11778             }
11779             return sum==0;
11780         case 4:
11781             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11782                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11783             }
11784             return sum==0;
11785     }
11786     return TRUE;
11787 }
11788
11789 #define Q_PROMO  4
11790 #define Q_EP     3
11791 #define Q_BCASTL 2
11792 #define Q_WCASTL 1
11793
11794 int pieceList[256], quickBoard[256];
11795 ChessSquare pieceType[256] = { EmptySquare };
11796 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11797 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11798 int soughtTotal, turn;
11799 Boolean epOK, flipSearch;
11800
11801 typedef struct {
11802     unsigned char piece, to;
11803 } Move;
11804
11805 #define DSIZE (250000)
11806
11807 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11808 Move *moveDatabase = initialSpace;
11809 unsigned int movePtr, dataSize = DSIZE;
11810
11811 int
11812 MakePieceList (Board board, int *counts)
11813 {
11814     int r, f, n=Q_PROMO, total=0;
11815     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11816     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817         int sq = f + (r<<4);
11818         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11819             quickBoard[sq] = ++n;
11820             pieceList[n] = sq;
11821             pieceType[n] = board[r][f];
11822             counts[board[r][f]]++;
11823             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11824             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11825             total++;
11826         }
11827     }
11828     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11829     return total;
11830 }
11831
11832 void
11833 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11834 {
11835     int sq = fromX + (fromY<<4);
11836     int piece = quickBoard[sq];
11837     quickBoard[sq] = 0;
11838     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11839     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11840         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11841         moveDatabase[movePtr++].piece = Q_WCASTL;
11842         quickBoard[sq] = piece;
11843         piece = quickBoard[from]; quickBoard[from] = 0;
11844         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11845     } else
11846     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11847         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11848         moveDatabase[movePtr++].piece = Q_BCASTL;
11849         quickBoard[sq] = piece;
11850         piece = quickBoard[from]; quickBoard[from] = 0;
11851         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11852     } else
11853     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11854         quickBoard[(fromY<<4)+toX] = 0;
11855         moveDatabase[movePtr].piece = Q_EP;
11856         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11857         moveDatabase[movePtr].to = sq;
11858     } else
11859     if(promoPiece != pieceType[piece]) {
11860         moveDatabase[movePtr++].piece = Q_PROMO;
11861         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11862     }
11863     moveDatabase[movePtr].piece = piece;
11864     quickBoard[sq] = piece;
11865     movePtr++;
11866 }
11867
11868 int
11869 PackGame (Board board)
11870 {
11871     Move *newSpace = NULL;
11872     moveDatabase[movePtr].piece = 0; // terminate previous game
11873     if(movePtr > dataSize) {
11874         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11875         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11876         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11877         if(newSpace) {
11878             int i;
11879             Move *p = moveDatabase, *q = newSpace;
11880             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11881             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11882             moveDatabase = newSpace;
11883         } else { // calloc failed, we must be out of memory. Too bad...
11884             dataSize = 0; // prevent calloc events for all subsequent games
11885             return 0;     // and signal this one isn't cached
11886         }
11887     }
11888     movePtr++;
11889     MakePieceList(board, counts);
11890     return movePtr;
11891 }
11892
11893 int
11894 QuickCompare (Board board, int *minCounts, int *maxCounts)
11895 {   // compare according to search mode
11896     int r, f;
11897     switch(appData.searchMode)
11898     {
11899       case 1: // exact position match
11900         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11901         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11902             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11903         }
11904         break;
11905       case 2: // can have extra material on empty squares
11906         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11907             if(board[r][f] == EmptySquare) continue;
11908             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11909         }
11910         break;
11911       case 3: // material with exact Pawn structure
11912         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11913             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11914             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11915         } // fall through to material comparison
11916       case 4: // exact material
11917         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11918         break;
11919       case 6: // material range with given imbalance
11920         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11921         // fall through to range comparison
11922       case 5: // material range
11923         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11924     }
11925     return TRUE;
11926 }
11927
11928 int
11929 QuickScan (Board board, Move *move)
11930 {   // reconstruct game,and compare all positions in it
11931     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11932     do {
11933         int piece = move->piece;
11934         int to = move->to, from = pieceList[piece];
11935         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11936           if(!piece) return -1;
11937           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11938             piece = (++move)->piece;
11939             from = pieceList[piece];
11940             counts[pieceType[piece]]--;
11941             pieceType[piece] = (ChessSquare) move->to;
11942             counts[move->to]++;
11943           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11944             counts[pieceType[quickBoard[to]]]--;
11945             quickBoard[to] = 0; total--;
11946             move++;
11947             continue;
11948           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11949             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11950             from  = pieceList[piece]; // so this must be King
11951             quickBoard[from] = 0;
11952             pieceList[piece] = to;
11953             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11954             quickBoard[from] = 0; // rook
11955             quickBoard[to] = piece;
11956             to = move->to; piece = move->piece;
11957             goto aftercastle;
11958           }
11959         }
11960         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11961         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11962         quickBoard[from] = 0;
11963       aftercastle:
11964         quickBoard[to] = piece;
11965         pieceList[piece] = to;
11966         cnt++; turn ^= 3;
11967         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11968            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11969            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11970                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11971           ) {
11972             static int lastCounts[EmptySquare+1];
11973             int i;
11974             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11975             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11976         } else stretch = 0;
11977         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11978         move++;
11979     } while(1);
11980 }
11981
11982 void
11983 InitSearch ()
11984 {
11985     int r, f;
11986     flipSearch = FALSE;
11987     CopyBoard(soughtBoard, boards[currentMove]);
11988     soughtTotal = MakePieceList(soughtBoard, maxSought);
11989     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11990     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11991     CopyBoard(reverseBoard, boards[currentMove]);
11992     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11993         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11994         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11995         reverseBoard[r][f] = piece;
11996     }
11997     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11998     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11999     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12000                  || (boards[currentMove][CASTLING][2] == NoRights ||
12001                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12002                  && (boards[currentMove][CASTLING][5] == NoRights ||
12003                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12004       ) {
12005         flipSearch = TRUE;
12006         CopyBoard(flipBoard, soughtBoard);
12007         CopyBoard(rotateBoard, reverseBoard);
12008         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12009             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12010             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12011         }
12012     }
12013     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12014     if(appData.searchMode >= 5) {
12015         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12016         MakePieceList(soughtBoard, minSought);
12017         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12018     }
12019     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12020         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12021 }
12022
12023 GameInfo dummyInfo;
12024 static int creatingBook;
12025
12026 int
12027 GameContainsPosition (FILE *f, ListGame *lg)
12028 {
12029     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12030     int fromX, fromY, toX, toY;
12031     char promoChar;
12032     static int initDone=FALSE;
12033
12034     // weed out games based on numerical tag comparison
12035     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12036     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12037     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12038     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12039     if(!initDone) {
12040         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12041         initDone = TRUE;
12042     }
12043     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12044     else CopyBoard(boards[scratch], initialPosition); // default start position
12045     if(lg->moves) {
12046         turn = btm + 1;
12047         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12048         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12049     }
12050     if(btm) plyNr++;
12051     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12052     fseek(f, lg->offset, 0);
12053     yynewfile(f);
12054     while(1) {
12055         yyboardindex = scratch;
12056         quickFlag = plyNr+1;
12057         next = Myylex();
12058         quickFlag = 0;
12059         switch(next) {
12060             case PGNTag:
12061                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12062             default:
12063                 continue;
12064
12065             case XBoardGame:
12066             case GNUChessGame:
12067                 if(plyNr) return -1; // after we have seen moves, this is for new game
12068               continue;
12069
12070             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12071             case ImpossibleMove:
12072             case WhiteWins: // game ends here with these four
12073             case BlackWins:
12074             case GameIsDrawn:
12075             case GameUnfinished:
12076                 return -1;
12077
12078             case IllegalMove:
12079                 if(appData.testLegality) return -1;
12080             case WhiteCapturesEnPassant:
12081             case BlackCapturesEnPassant:
12082             case WhitePromotion:
12083             case BlackPromotion:
12084             case WhiteNonPromotion:
12085             case BlackNonPromotion:
12086             case NormalMove:
12087             case WhiteKingSideCastle:
12088             case WhiteQueenSideCastle:
12089             case BlackKingSideCastle:
12090             case BlackQueenSideCastle:
12091             case WhiteKingSideCastleWild:
12092             case WhiteQueenSideCastleWild:
12093             case BlackKingSideCastleWild:
12094             case BlackQueenSideCastleWild:
12095             case WhiteHSideCastleFR:
12096             case WhiteASideCastleFR:
12097             case BlackHSideCastleFR:
12098             case BlackASideCastleFR:
12099                 fromX = currentMoveString[0] - AAA;
12100                 fromY = currentMoveString[1] - ONE;
12101                 toX = currentMoveString[2] - AAA;
12102                 toY = currentMoveString[3] - ONE;
12103                 promoChar = currentMoveString[4];
12104                 break;
12105             case WhiteDrop:
12106             case BlackDrop:
12107                 fromX = next == WhiteDrop ?
12108                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12109                   (int) CharToPiece(ToLower(currentMoveString[0]));
12110                 fromY = DROP_RANK;
12111                 toX = currentMoveString[2] - AAA;
12112                 toY = currentMoveString[3] - ONE;
12113                 promoChar = 0;
12114                 break;
12115         }
12116         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12117         plyNr++;
12118         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12119         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12120         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12121         if(appData.findMirror) {
12122             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12123             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12124         }
12125     }
12126 }
12127
12128 /* Load the nth game from open file f */
12129 int
12130 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12131 {
12132     ChessMove cm;
12133     char buf[MSG_SIZ];
12134     int gn = gameNumber;
12135     ListGame *lg = NULL;
12136     int numPGNTags = 0;
12137     int err, pos = -1;
12138     GameMode oldGameMode;
12139     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12140
12141     if (appData.debugMode)
12142         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12143
12144     if (gameMode == Training )
12145         SetTrainingModeOff();
12146
12147     oldGameMode = gameMode;
12148     if (gameMode != BeginningOfGame) {
12149       Reset(FALSE, TRUE);
12150     }
12151
12152     gameFileFP = f;
12153     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12154         fclose(lastLoadGameFP);
12155     }
12156
12157     if (useList) {
12158         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12159
12160         if (lg) {
12161             fseek(f, lg->offset, 0);
12162             GameListHighlight(gameNumber);
12163             pos = lg->position;
12164             gn = 1;
12165         }
12166         else {
12167             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12168               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12169             else
12170             DisplayError(_("Game number out of range"), 0);
12171             return FALSE;
12172         }
12173     } else {
12174         GameListDestroy();
12175         if (fseek(f, 0, 0) == -1) {
12176             if (f == lastLoadGameFP ?
12177                 gameNumber == lastLoadGameNumber + 1 :
12178                 gameNumber == 1) {
12179                 gn = 1;
12180             } else {
12181                 DisplayError(_("Can't seek on game file"), 0);
12182                 return FALSE;
12183             }
12184         }
12185     }
12186     lastLoadGameFP = f;
12187     lastLoadGameNumber = gameNumber;
12188     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12189     lastLoadGameUseList = useList;
12190
12191     yynewfile(f);
12192
12193     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12194       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12195                 lg->gameInfo.black);
12196             DisplayTitle(buf);
12197     } else if (*title != NULLCHAR) {
12198         if (gameNumber > 1) {
12199           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12200             DisplayTitle(buf);
12201         } else {
12202             DisplayTitle(title);
12203         }
12204     }
12205
12206     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12207         gameMode = PlayFromGameFile;
12208         ModeHighlight();
12209     }
12210
12211     currentMove = forwardMostMove = backwardMostMove = 0;
12212     CopyBoard(boards[0], initialPosition);
12213     StopClocks();
12214
12215     /*
12216      * Skip the first gn-1 games in the file.
12217      * Also skip over anything that precedes an identifiable
12218      * start of game marker, to avoid being confused by
12219      * garbage at the start of the file.  Currently
12220      * recognized start of game markers are the move number "1",
12221      * the pattern "gnuchess .* game", the pattern
12222      * "^[#;%] [^ ]* game file", and a PGN tag block.
12223      * A game that starts with one of the latter two patterns
12224      * will also have a move number 1, possibly
12225      * following a position diagram.
12226      * 5-4-02: Let's try being more lenient and allowing a game to
12227      * start with an unnumbered move.  Does that break anything?
12228      */
12229     cm = lastLoadGameStart = EndOfFile;
12230     while (gn > 0) {
12231         yyboardindex = forwardMostMove;
12232         cm = (ChessMove) Myylex();
12233         switch (cm) {
12234           case EndOfFile:
12235             if (cmailMsgLoaded) {
12236                 nCmailGames = CMAIL_MAX_GAMES - gn;
12237             } else {
12238                 Reset(TRUE, TRUE);
12239                 DisplayError(_("Game not found in file"), 0);
12240             }
12241             return FALSE;
12242
12243           case GNUChessGame:
12244           case XBoardGame:
12245             gn--;
12246             lastLoadGameStart = cm;
12247             break;
12248
12249           case MoveNumberOne:
12250             switch (lastLoadGameStart) {
12251               case GNUChessGame:
12252               case XBoardGame:
12253               case PGNTag:
12254                 break;
12255               case MoveNumberOne:
12256               case EndOfFile:
12257                 gn--;           /* count this game */
12258                 lastLoadGameStart = cm;
12259                 break;
12260               default:
12261                 /* impossible */
12262                 break;
12263             }
12264             break;
12265
12266           case PGNTag:
12267             switch (lastLoadGameStart) {
12268               case GNUChessGame:
12269               case PGNTag:
12270               case MoveNumberOne:
12271               case EndOfFile:
12272                 gn--;           /* count this game */
12273                 lastLoadGameStart = cm;
12274                 break;
12275               case XBoardGame:
12276                 lastLoadGameStart = cm; /* game counted already */
12277                 break;
12278               default:
12279                 /* impossible */
12280                 break;
12281             }
12282             if (gn > 0) {
12283                 do {
12284                     yyboardindex = forwardMostMove;
12285                     cm = (ChessMove) Myylex();
12286                 } while (cm == PGNTag || cm == Comment);
12287             }
12288             break;
12289
12290           case WhiteWins:
12291           case BlackWins:
12292           case GameIsDrawn:
12293             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12294                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12295                     != CMAIL_OLD_RESULT) {
12296                     nCmailResults ++ ;
12297                     cmailResult[  CMAIL_MAX_GAMES
12298                                 - gn - 1] = CMAIL_OLD_RESULT;
12299                 }
12300             }
12301             break;
12302
12303           case NormalMove:
12304             /* Only a NormalMove can be at the start of a game
12305              * without a position diagram. */
12306             if (lastLoadGameStart == EndOfFile ) {
12307               gn--;
12308               lastLoadGameStart = MoveNumberOne;
12309             }
12310             break;
12311
12312           default:
12313             break;
12314         }
12315     }
12316
12317     if (appData.debugMode)
12318       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12319
12320     if (cm == XBoardGame) {
12321         /* Skip any header junk before position diagram and/or move 1 */
12322         for (;;) {
12323             yyboardindex = forwardMostMove;
12324             cm = (ChessMove) Myylex();
12325
12326             if (cm == EndOfFile ||
12327                 cm == GNUChessGame || cm == XBoardGame) {
12328                 /* Empty game; pretend end-of-file and handle later */
12329                 cm = EndOfFile;
12330                 break;
12331             }
12332
12333             if (cm == MoveNumberOne || cm == PositionDiagram ||
12334                 cm == PGNTag || cm == Comment)
12335               break;
12336         }
12337     } else if (cm == GNUChessGame) {
12338         if (gameInfo.event != NULL) {
12339             free(gameInfo.event);
12340         }
12341         gameInfo.event = StrSave(yy_text);
12342     }
12343
12344     startedFromSetupPosition = FALSE;
12345     while (cm == PGNTag) {
12346         if (appData.debugMode)
12347           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12348         err = ParsePGNTag(yy_text, &gameInfo);
12349         if (!err) numPGNTags++;
12350
12351         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12352         if(gameInfo.variant != oldVariant) {
12353             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12354             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12355             InitPosition(TRUE);
12356             oldVariant = gameInfo.variant;
12357             if (appData.debugMode)
12358               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12359         }
12360
12361
12362         if (gameInfo.fen != NULL) {
12363           Board initial_position;
12364           startedFromSetupPosition = TRUE;
12365           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12366             Reset(TRUE, TRUE);
12367             DisplayError(_("Bad FEN position in file"), 0);
12368             return FALSE;
12369           }
12370           CopyBoard(boards[0], initial_position);
12371           if (blackPlaysFirst) {
12372             currentMove = forwardMostMove = backwardMostMove = 1;
12373             CopyBoard(boards[1], initial_position);
12374             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12375             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12376             timeRemaining[0][1] = whiteTimeRemaining;
12377             timeRemaining[1][1] = blackTimeRemaining;
12378             if (commentList[0] != NULL) {
12379               commentList[1] = commentList[0];
12380               commentList[0] = NULL;
12381             }
12382           } else {
12383             currentMove = forwardMostMove = backwardMostMove = 0;
12384           }
12385           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12386           {   int i;
12387               initialRulePlies = FENrulePlies;
12388               for( i=0; i< nrCastlingRights; i++ )
12389                   initialRights[i] = initial_position[CASTLING][i];
12390           }
12391           yyboardindex = forwardMostMove;
12392           free(gameInfo.fen);
12393           gameInfo.fen = NULL;
12394         }
12395
12396         yyboardindex = forwardMostMove;
12397         cm = (ChessMove) Myylex();
12398
12399         /* Handle comments interspersed among the tags */
12400         while (cm == Comment) {
12401             char *p;
12402             if (appData.debugMode)
12403               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12404             p = yy_text;
12405             AppendComment(currentMove, p, FALSE);
12406             yyboardindex = forwardMostMove;
12407             cm = (ChessMove) Myylex();
12408         }
12409     }
12410
12411     /* don't rely on existence of Event tag since if game was
12412      * pasted from clipboard the Event tag may not exist
12413      */
12414     if (numPGNTags > 0){
12415         char *tags;
12416         if (gameInfo.variant == VariantNormal) {
12417           VariantClass v = StringToVariant(gameInfo.event);
12418           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12419           if(v < VariantShogi) gameInfo.variant = v;
12420         }
12421         if (!matchMode) {
12422           if( appData.autoDisplayTags ) {
12423             tags = PGNTags(&gameInfo);
12424             TagsPopUp(tags, CmailMsg());
12425             free(tags);
12426           }
12427         }
12428     } else {
12429         /* Make something up, but don't display it now */
12430         SetGameInfo();
12431         TagsPopDown();
12432     }
12433
12434     if (cm == PositionDiagram) {
12435         int i, j;
12436         char *p;
12437         Board initial_position;
12438
12439         if (appData.debugMode)
12440           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12441
12442         if (!startedFromSetupPosition) {
12443             p = yy_text;
12444             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12445               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12446                 switch (*p) {
12447                   case '{':
12448                   case '[':
12449                   case '-':
12450                   case ' ':
12451                   case '\t':
12452                   case '\n':
12453                   case '\r':
12454                     break;
12455                   default:
12456                     initial_position[i][j++] = CharToPiece(*p);
12457                     break;
12458                 }
12459             while (*p == ' ' || *p == '\t' ||
12460                    *p == '\n' || *p == '\r') p++;
12461
12462             if (strncmp(p, "black", strlen("black"))==0)
12463               blackPlaysFirst = TRUE;
12464             else
12465               blackPlaysFirst = FALSE;
12466             startedFromSetupPosition = TRUE;
12467
12468             CopyBoard(boards[0], initial_position);
12469             if (blackPlaysFirst) {
12470                 currentMove = forwardMostMove = backwardMostMove = 1;
12471                 CopyBoard(boards[1], initial_position);
12472                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12473                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12474                 timeRemaining[0][1] = whiteTimeRemaining;
12475                 timeRemaining[1][1] = blackTimeRemaining;
12476                 if (commentList[0] != NULL) {
12477                     commentList[1] = commentList[0];
12478                     commentList[0] = NULL;
12479                 }
12480             } else {
12481                 currentMove = forwardMostMove = backwardMostMove = 0;
12482             }
12483         }
12484         yyboardindex = forwardMostMove;
12485         cm = (ChessMove) Myylex();
12486     }
12487
12488   if(!creatingBook) {
12489     if (first.pr == NoProc) {
12490         StartChessProgram(&first);
12491     }
12492     InitChessProgram(&first, FALSE);
12493     SendToProgram("force\n", &first);
12494     if (startedFromSetupPosition) {
12495         SendBoard(&first, forwardMostMove);
12496     if (appData.debugMode) {
12497         fprintf(debugFP, "Load Game\n");
12498     }
12499         DisplayBothClocks();
12500     }
12501   }
12502
12503     /* [HGM] server: flag to write setup moves in broadcast file as one */
12504     loadFlag = appData.suppressLoadMoves;
12505
12506     while (cm == Comment) {
12507         char *p;
12508         if (appData.debugMode)
12509           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12510         p = yy_text;
12511         AppendComment(currentMove, p, FALSE);
12512         yyboardindex = forwardMostMove;
12513         cm = (ChessMove) Myylex();
12514     }
12515
12516     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12517         cm == WhiteWins || cm == BlackWins ||
12518         cm == GameIsDrawn || cm == GameUnfinished) {
12519         DisplayMessage("", _("No moves in game"));
12520         if (cmailMsgLoaded) {
12521             if (appData.debugMode)
12522               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12523             ClearHighlights();
12524             flipView = FALSE;
12525         }
12526         DrawPosition(FALSE, boards[currentMove]);
12527         DisplayBothClocks();
12528         gameMode = EditGame;
12529         ModeHighlight();
12530         gameFileFP = NULL;
12531         cmailOldMove = 0;
12532         return TRUE;
12533     }
12534
12535     // [HGM] PV info: routine tests if comment empty
12536     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12537         DisplayComment(currentMove - 1, commentList[currentMove]);
12538     }
12539     if (!matchMode && appData.timeDelay != 0)
12540       DrawPosition(FALSE, boards[currentMove]);
12541
12542     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12543       programStats.ok_to_send = 1;
12544     }
12545
12546     /* if the first token after the PGN tags is a move
12547      * and not move number 1, retrieve it from the parser
12548      */
12549     if (cm != MoveNumberOne)
12550         LoadGameOneMove(cm);
12551
12552     /* load the remaining moves from the file */
12553     while (LoadGameOneMove(EndOfFile)) {
12554       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12555       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12556     }
12557
12558     /* rewind to the start of the game */
12559     currentMove = backwardMostMove;
12560
12561     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12562
12563     if (oldGameMode == AnalyzeFile) {
12564       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12565       AnalyzeFileEvent();
12566     } else
12567     if (oldGameMode == AnalyzeMode) {
12568       AnalyzeFileEvent();
12569     }
12570
12571     if(creatingBook) return TRUE;
12572     if (!matchMode && pos > 0) {
12573         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12574     } else
12575     if (matchMode || appData.timeDelay == 0) {
12576       ToEndEvent();
12577     } else if (appData.timeDelay > 0) {
12578       AutoPlayGameLoop();
12579     }
12580
12581     if (appData.debugMode)
12582         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12583
12584     loadFlag = 0; /* [HGM] true game starts */
12585     return TRUE;
12586 }
12587
12588 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12589 int
12590 ReloadPosition (int offset)
12591 {
12592     int positionNumber = lastLoadPositionNumber + offset;
12593     if (lastLoadPositionFP == NULL) {
12594         DisplayError(_("No position has been loaded yet"), 0);
12595         return FALSE;
12596     }
12597     if (positionNumber <= 0) {
12598         DisplayError(_("Can't back up any further"), 0);
12599         return FALSE;
12600     }
12601     return LoadPosition(lastLoadPositionFP, positionNumber,
12602                         lastLoadPositionTitle);
12603 }
12604
12605 /* Load the nth position from the given file */
12606 int
12607 LoadPositionFromFile (char *filename, int n, char *title)
12608 {
12609     FILE *f;
12610     char buf[MSG_SIZ];
12611
12612     if (strcmp(filename, "-") == 0) {
12613         return LoadPosition(stdin, n, "stdin");
12614     } else {
12615         f = fopen(filename, "rb");
12616         if (f == NULL) {
12617             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12618             DisplayError(buf, errno);
12619             return FALSE;
12620         } else {
12621             return LoadPosition(f, n, title);
12622         }
12623     }
12624 }
12625
12626 /* Load the nth position from the given open file, and close it */
12627 int
12628 LoadPosition (FILE *f, int positionNumber, char *title)
12629 {
12630     char *p, line[MSG_SIZ];
12631     Board initial_position;
12632     int i, j, fenMode, pn;
12633
12634     if (gameMode == Training )
12635         SetTrainingModeOff();
12636
12637     if (gameMode != BeginningOfGame) {
12638         Reset(FALSE, TRUE);
12639     }
12640     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12641         fclose(lastLoadPositionFP);
12642     }
12643     if (positionNumber == 0) positionNumber = 1;
12644     lastLoadPositionFP = f;
12645     lastLoadPositionNumber = positionNumber;
12646     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12647     if (first.pr == NoProc && !appData.noChessProgram) {
12648       StartChessProgram(&first);
12649       InitChessProgram(&first, FALSE);
12650     }
12651     pn = positionNumber;
12652     if (positionNumber < 0) {
12653         /* Negative position number means to seek to that byte offset */
12654         if (fseek(f, -positionNumber, 0) == -1) {
12655             DisplayError(_("Can't seek on position file"), 0);
12656             return FALSE;
12657         };
12658         pn = 1;
12659     } else {
12660         if (fseek(f, 0, 0) == -1) {
12661             if (f == lastLoadPositionFP ?
12662                 positionNumber == lastLoadPositionNumber + 1 :
12663                 positionNumber == 1) {
12664                 pn = 1;
12665             } else {
12666                 DisplayError(_("Can't seek on position file"), 0);
12667                 return FALSE;
12668             }
12669         }
12670     }
12671     /* See if this file is FEN or old-style xboard */
12672     if (fgets(line, MSG_SIZ, f) == NULL) {
12673         DisplayError(_("Position not found in file"), 0);
12674         return FALSE;
12675     }
12676     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12677     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12678
12679     if (pn >= 2) {
12680         if (fenMode || line[0] == '#') pn--;
12681         while (pn > 0) {
12682             /* skip positions before number pn */
12683             if (fgets(line, MSG_SIZ, f) == NULL) {
12684                 Reset(TRUE, TRUE);
12685                 DisplayError(_("Position not found in file"), 0);
12686                 return FALSE;
12687             }
12688             if (fenMode || line[0] == '#') pn--;
12689         }
12690     }
12691
12692     if (fenMode) {
12693         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12694             DisplayError(_("Bad FEN position in file"), 0);
12695             return FALSE;
12696         }
12697     } else {
12698         (void) fgets(line, MSG_SIZ, f);
12699         (void) fgets(line, MSG_SIZ, f);
12700
12701         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12702             (void) fgets(line, MSG_SIZ, f);
12703             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12704                 if (*p == ' ')
12705                   continue;
12706                 initial_position[i][j++] = CharToPiece(*p);
12707             }
12708         }
12709
12710         blackPlaysFirst = FALSE;
12711         if (!feof(f)) {
12712             (void) fgets(line, MSG_SIZ, f);
12713             if (strncmp(line, "black", strlen("black"))==0)
12714               blackPlaysFirst = TRUE;
12715         }
12716     }
12717     startedFromSetupPosition = TRUE;
12718
12719     CopyBoard(boards[0], initial_position);
12720     if (blackPlaysFirst) {
12721         currentMove = forwardMostMove = backwardMostMove = 1;
12722         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12723         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12724         CopyBoard(boards[1], initial_position);
12725         DisplayMessage("", _("Black to play"));
12726     } else {
12727         currentMove = forwardMostMove = backwardMostMove = 0;
12728         DisplayMessage("", _("White to play"));
12729     }
12730     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12731     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12732         SendToProgram("force\n", &first);
12733         SendBoard(&first, forwardMostMove);
12734     }
12735     if (appData.debugMode) {
12736 int i, j;
12737   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12738   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12739         fprintf(debugFP, "Load Position\n");
12740     }
12741
12742     if (positionNumber > 1) {
12743       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12744         DisplayTitle(line);
12745     } else {
12746         DisplayTitle(title);
12747     }
12748     gameMode = EditGame;
12749     ModeHighlight();
12750     ResetClocks();
12751     timeRemaining[0][1] = whiteTimeRemaining;
12752     timeRemaining[1][1] = blackTimeRemaining;
12753     DrawPosition(FALSE, boards[currentMove]);
12754
12755     return TRUE;
12756 }
12757
12758
12759 void
12760 CopyPlayerNameIntoFileName (char **dest, char *src)
12761 {
12762     while (*src != NULLCHAR && *src != ',') {
12763         if (*src == ' ') {
12764             *(*dest)++ = '_';
12765             src++;
12766         } else {
12767             *(*dest)++ = *src++;
12768         }
12769     }
12770 }
12771
12772 char *
12773 DefaultFileName (char *ext)
12774 {
12775     static char def[MSG_SIZ];
12776     char *p;
12777
12778     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12779         p = def;
12780         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12781         *p++ = '-';
12782         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12783         *p++ = '.';
12784         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12785     } else {
12786         def[0] = NULLCHAR;
12787     }
12788     return def;
12789 }
12790
12791 /* Save the current game to the given file */
12792 int
12793 SaveGameToFile (char *filename, int append)
12794 {
12795     FILE *f;
12796     char buf[MSG_SIZ];
12797     int result, i, t,tot=0;
12798
12799     if (strcmp(filename, "-") == 0) {
12800         return SaveGame(stdout, 0, NULL);
12801     } else {
12802         for(i=0; i<10; i++) { // upto 10 tries
12803              f = fopen(filename, append ? "a" : "w");
12804              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12805              if(f || errno != 13) break;
12806              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12807              tot += t;
12808         }
12809         if (f == NULL) {
12810             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12811             DisplayError(buf, errno);
12812             return FALSE;
12813         } else {
12814             safeStrCpy(buf, lastMsg, MSG_SIZ);
12815             DisplayMessage(_("Waiting for access to save file"), "");
12816             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12817             DisplayMessage(_("Saving game"), "");
12818             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12819             result = SaveGame(f, 0, NULL);
12820             DisplayMessage(buf, "");
12821             return result;
12822         }
12823     }
12824 }
12825
12826 char *
12827 SavePart (char *str)
12828 {
12829     static char buf[MSG_SIZ];
12830     char *p;
12831
12832     p = strchr(str, ' ');
12833     if (p == NULL) return str;
12834     strncpy(buf, str, p - str);
12835     buf[p - str] = NULLCHAR;
12836     return buf;
12837 }
12838
12839 #define PGN_MAX_LINE 75
12840
12841 #define PGN_SIDE_WHITE  0
12842 #define PGN_SIDE_BLACK  1
12843
12844 static int
12845 FindFirstMoveOutOfBook (int side)
12846 {
12847     int result = -1;
12848
12849     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12850         int index = backwardMostMove;
12851         int has_book_hit = 0;
12852
12853         if( (index % 2) != side ) {
12854             index++;
12855         }
12856
12857         while( index < forwardMostMove ) {
12858             /* Check to see if engine is in book */
12859             int depth = pvInfoList[index].depth;
12860             int score = pvInfoList[index].score;
12861             int in_book = 0;
12862
12863             if( depth <= 2 ) {
12864                 in_book = 1;
12865             }
12866             else if( score == 0 && depth == 63 ) {
12867                 in_book = 1; /* Zappa */
12868             }
12869             else if( score == 2 && depth == 99 ) {
12870                 in_book = 1; /* Abrok */
12871             }
12872
12873             has_book_hit += in_book;
12874
12875             if( ! in_book ) {
12876                 result = index;
12877
12878                 break;
12879             }
12880
12881             index += 2;
12882         }
12883     }
12884
12885     return result;
12886 }
12887
12888 void
12889 GetOutOfBookInfo (char * buf)
12890 {
12891     int oob[2];
12892     int i;
12893     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12894
12895     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12896     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12897
12898     *buf = '\0';
12899
12900     if( oob[0] >= 0 || oob[1] >= 0 ) {
12901         for( i=0; i<2; i++ ) {
12902             int idx = oob[i];
12903
12904             if( idx >= 0 ) {
12905                 if( i > 0 && oob[0] >= 0 ) {
12906                     strcat( buf, "   " );
12907                 }
12908
12909                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12910                 sprintf( buf+strlen(buf), "%s%.2f",
12911                     pvInfoList[idx].score >= 0 ? "+" : "",
12912                     pvInfoList[idx].score / 100.0 );
12913             }
12914         }
12915     }
12916 }
12917
12918 /* Save game in PGN style and close the file */
12919 int
12920 SaveGamePGN (FILE *f)
12921 {
12922     int i, offset, linelen, newblock;
12923 //    char *movetext;
12924     char numtext[32];
12925     int movelen, numlen, blank;
12926     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12927
12928     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12929
12930     PrintPGNTags(f, &gameInfo);
12931
12932     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12933
12934     if (backwardMostMove > 0 || startedFromSetupPosition) {
12935         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12936         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12937         fprintf(f, "\n{--------------\n");
12938         PrintPosition(f, backwardMostMove);
12939         fprintf(f, "--------------}\n");
12940         free(fen);
12941     }
12942     else {
12943         /* [AS] Out of book annotation */
12944         if( appData.saveOutOfBookInfo ) {
12945             char buf[64];
12946
12947             GetOutOfBookInfo( buf );
12948
12949             if( buf[0] != '\0' ) {
12950                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12951             }
12952         }
12953
12954         fprintf(f, "\n");
12955     }
12956
12957     i = backwardMostMove;
12958     linelen = 0;
12959     newblock = TRUE;
12960
12961     while (i < forwardMostMove) {
12962         /* Print comments preceding this move */
12963         if (commentList[i] != NULL) {
12964             if (linelen > 0) fprintf(f, "\n");
12965             fprintf(f, "%s", commentList[i]);
12966             linelen = 0;
12967             newblock = TRUE;
12968         }
12969
12970         /* Format move number */
12971         if ((i % 2) == 0)
12972           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12973         else
12974           if (newblock)
12975             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12976           else
12977             numtext[0] = NULLCHAR;
12978
12979         numlen = strlen(numtext);
12980         newblock = FALSE;
12981
12982         /* Print move number */
12983         blank = linelen > 0 && numlen > 0;
12984         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12985             fprintf(f, "\n");
12986             linelen = 0;
12987             blank = 0;
12988         }
12989         if (blank) {
12990             fprintf(f, " ");
12991             linelen++;
12992         }
12993         fprintf(f, "%s", numtext);
12994         linelen += numlen;
12995
12996         /* Get move */
12997         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12998         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12999
13000         /* Print move */
13001         blank = linelen > 0 && movelen > 0;
13002         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13003             fprintf(f, "\n");
13004             linelen = 0;
13005             blank = 0;
13006         }
13007         if (blank) {
13008             fprintf(f, " ");
13009             linelen++;
13010         }
13011         fprintf(f, "%s", move_buffer);
13012         linelen += movelen;
13013
13014         /* [AS] Add PV info if present */
13015         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13016             /* [HGM] add time */
13017             char buf[MSG_SIZ]; int seconds;
13018
13019             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13020
13021             if( seconds <= 0)
13022               buf[0] = 0;
13023             else
13024               if( seconds < 30 )
13025                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13026               else
13027                 {
13028                   seconds = (seconds + 4)/10; // round to full seconds
13029                   if( seconds < 60 )
13030                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13031                   else
13032                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13033                 }
13034
13035             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13036                       pvInfoList[i].score >= 0 ? "+" : "",
13037                       pvInfoList[i].score / 100.0,
13038                       pvInfoList[i].depth,
13039                       buf );
13040
13041             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13042
13043             /* Print score/depth */
13044             blank = linelen > 0 && movelen > 0;
13045             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13046                 fprintf(f, "\n");
13047                 linelen = 0;
13048                 blank = 0;
13049             }
13050             if (blank) {
13051                 fprintf(f, " ");
13052                 linelen++;
13053             }
13054             fprintf(f, "%s", move_buffer);
13055             linelen += movelen;
13056         }
13057
13058         i++;
13059     }
13060
13061     /* Start a new line */
13062     if (linelen > 0) fprintf(f, "\n");
13063
13064     /* Print comments after last move */
13065     if (commentList[i] != NULL) {
13066         fprintf(f, "%s\n", commentList[i]);
13067     }
13068
13069     /* Print result */
13070     if (gameInfo.resultDetails != NULL &&
13071         gameInfo.resultDetails[0] != NULLCHAR) {
13072         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13073                 PGNResult(gameInfo.result));
13074     } else {
13075         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13076     }
13077
13078     fclose(f);
13079     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13080     return TRUE;
13081 }
13082
13083 /* Save game in old style and close the file */
13084 int
13085 SaveGameOldStyle (FILE *f)
13086 {
13087     int i, offset;
13088     time_t tm;
13089
13090     tm = time((time_t *) NULL);
13091
13092     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13093     PrintOpponents(f);
13094
13095     if (backwardMostMove > 0 || startedFromSetupPosition) {
13096         fprintf(f, "\n[--------------\n");
13097         PrintPosition(f, backwardMostMove);
13098         fprintf(f, "--------------]\n");
13099     } else {
13100         fprintf(f, "\n");
13101     }
13102
13103     i = backwardMostMove;
13104     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13105
13106     while (i < forwardMostMove) {
13107         if (commentList[i] != NULL) {
13108             fprintf(f, "[%s]\n", commentList[i]);
13109         }
13110
13111         if ((i % 2) == 1) {
13112             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13113             i++;
13114         } else {
13115             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13116             i++;
13117             if (commentList[i] != NULL) {
13118                 fprintf(f, "\n");
13119                 continue;
13120             }
13121             if (i >= forwardMostMove) {
13122                 fprintf(f, "\n");
13123                 break;
13124             }
13125             fprintf(f, "%s\n", parseList[i]);
13126             i++;
13127         }
13128     }
13129
13130     if (commentList[i] != NULL) {
13131         fprintf(f, "[%s]\n", commentList[i]);
13132     }
13133
13134     /* This isn't really the old style, but it's close enough */
13135     if (gameInfo.resultDetails != NULL &&
13136         gameInfo.resultDetails[0] != NULLCHAR) {
13137         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13138                 gameInfo.resultDetails);
13139     } else {
13140         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13141     }
13142
13143     fclose(f);
13144     return TRUE;
13145 }
13146
13147 /* Save the current game to open file f and close the file */
13148 int
13149 SaveGame (FILE *f, int dummy, char *dummy2)
13150 {
13151     if (gameMode == EditPosition) EditPositionDone(TRUE);
13152     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13153     if (appData.oldSaveStyle)
13154       return SaveGameOldStyle(f);
13155     else
13156       return SaveGamePGN(f);
13157 }
13158
13159 /* Save the current position to the given file */
13160 int
13161 SavePositionToFile (char *filename)
13162 {
13163     FILE *f;
13164     char buf[MSG_SIZ];
13165
13166     if (strcmp(filename, "-") == 0) {
13167         return SavePosition(stdout, 0, NULL);
13168     } else {
13169         f = fopen(filename, "a");
13170         if (f == NULL) {
13171             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13172             DisplayError(buf, errno);
13173             return FALSE;
13174         } else {
13175             safeStrCpy(buf, lastMsg, MSG_SIZ);
13176             DisplayMessage(_("Waiting for access to save file"), "");
13177             flock(fileno(f), LOCK_EX); // [HGM] lock
13178             DisplayMessage(_("Saving position"), "");
13179             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13180             SavePosition(f, 0, NULL);
13181             DisplayMessage(buf, "");
13182             return TRUE;
13183         }
13184     }
13185 }
13186
13187 /* Save the current position to the given open file and close the file */
13188 int
13189 SavePosition (FILE *f, int dummy, char *dummy2)
13190 {
13191     time_t tm;
13192     char *fen;
13193
13194     if (gameMode == EditPosition) EditPositionDone(TRUE);
13195     if (appData.oldSaveStyle) {
13196         tm = time((time_t *) NULL);
13197
13198         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13199         PrintOpponents(f);
13200         fprintf(f, "[--------------\n");
13201         PrintPosition(f, currentMove);
13202         fprintf(f, "--------------]\n");
13203     } else {
13204         fen = PositionToFEN(currentMove, NULL, 1);
13205         fprintf(f, "%s\n", fen);
13206         free(fen);
13207     }
13208     fclose(f);
13209     return TRUE;
13210 }
13211
13212 void
13213 ReloadCmailMsgEvent (int unregister)
13214 {
13215 #if !WIN32
13216     static char *inFilename = NULL;
13217     static char *outFilename;
13218     int i;
13219     struct stat inbuf, outbuf;
13220     int status;
13221
13222     /* Any registered moves are unregistered if unregister is set, */
13223     /* i.e. invoked by the signal handler */
13224     if (unregister) {
13225         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13226             cmailMoveRegistered[i] = FALSE;
13227             if (cmailCommentList[i] != NULL) {
13228                 free(cmailCommentList[i]);
13229                 cmailCommentList[i] = NULL;
13230             }
13231         }
13232         nCmailMovesRegistered = 0;
13233     }
13234
13235     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13236         cmailResult[i] = CMAIL_NOT_RESULT;
13237     }
13238     nCmailResults = 0;
13239
13240     if (inFilename == NULL) {
13241         /* Because the filenames are static they only get malloced once  */
13242         /* and they never get freed                                      */
13243         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13244         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13245
13246         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13247         sprintf(outFilename, "%s.out", appData.cmailGameName);
13248     }
13249
13250     status = stat(outFilename, &outbuf);
13251     if (status < 0) {
13252         cmailMailedMove = FALSE;
13253     } else {
13254         status = stat(inFilename, &inbuf);
13255         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13256     }
13257
13258     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13259        counts the games, notes how each one terminated, etc.
13260
13261        It would be nice to remove this kludge and instead gather all
13262        the information while building the game list.  (And to keep it
13263        in the game list nodes instead of having a bunch of fixed-size
13264        parallel arrays.)  Note this will require getting each game's
13265        termination from the PGN tags, as the game list builder does
13266        not process the game moves.  --mann
13267        */
13268     cmailMsgLoaded = TRUE;
13269     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13270
13271     /* Load first game in the file or popup game menu */
13272     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13273
13274 #endif /* !WIN32 */
13275     return;
13276 }
13277
13278 int
13279 RegisterMove ()
13280 {
13281     FILE *f;
13282     char string[MSG_SIZ];
13283
13284     if (   cmailMailedMove
13285         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13286         return TRUE;            /* Allow free viewing  */
13287     }
13288
13289     /* Unregister move to ensure that we don't leave RegisterMove        */
13290     /* with the move registered when the conditions for registering no   */
13291     /* longer hold                                                       */
13292     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13293         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13294         nCmailMovesRegistered --;
13295
13296         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13297           {
13298               free(cmailCommentList[lastLoadGameNumber - 1]);
13299               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13300           }
13301     }
13302
13303     if (cmailOldMove == -1) {
13304         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13305         return FALSE;
13306     }
13307
13308     if (currentMove > cmailOldMove + 1) {
13309         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13310         return FALSE;
13311     }
13312
13313     if (currentMove < cmailOldMove) {
13314         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13315         return FALSE;
13316     }
13317
13318     if (forwardMostMove > currentMove) {
13319         /* Silently truncate extra moves */
13320         TruncateGame();
13321     }
13322
13323     if (   (currentMove == cmailOldMove + 1)
13324         || (   (currentMove == cmailOldMove)
13325             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13326                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13327         if (gameInfo.result != GameUnfinished) {
13328             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13329         }
13330
13331         if (commentList[currentMove] != NULL) {
13332             cmailCommentList[lastLoadGameNumber - 1]
13333               = StrSave(commentList[currentMove]);
13334         }
13335         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13336
13337         if (appData.debugMode)
13338           fprintf(debugFP, "Saving %s for game %d\n",
13339                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13340
13341         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13342
13343         f = fopen(string, "w");
13344         if (appData.oldSaveStyle) {
13345             SaveGameOldStyle(f); /* also closes the file */
13346
13347             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13348             f = fopen(string, "w");
13349             SavePosition(f, 0, NULL); /* also closes the file */
13350         } else {
13351             fprintf(f, "{--------------\n");
13352             PrintPosition(f, currentMove);
13353             fprintf(f, "--------------}\n\n");
13354
13355             SaveGame(f, 0, NULL); /* also closes the file*/
13356         }
13357
13358         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13359         nCmailMovesRegistered ++;
13360     } else if (nCmailGames == 1) {
13361         DisplayError(_("You have not made a move yet"), 0);
13362         return FALSE;
13363     }
13364
13365     return TRUE;
13366 }
13367
13368 void
13369 MailMoveEvent ()
13370 {
13371 #if !WIN32
13372     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13373     FILE *commandOutput;
13374     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13375     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13376     int nBuffers;
13377     int i;
13378     int archived;
13379     char *arcDir;
13380
13381     if (! cmailMsgLoaded) {
13382         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13383         return;
13384     }
13385
13386     if (nCmailGames == nCmailResults) {
13387         DisplayError(_("No unfinished games"), 0);
13388         return;
13389     }
13390
13391 #if CMAIL_PROHIBIT_REMAIL
13392     if (cmailMailedMove) {
13393       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);
13394         DisplayError(msg, 0);
13395         return;
13396     }
13397 #endif
13398
13399     if (! (cmailMailedMove || RegisterMove())) return;
13400
13401     if (   cmailMailedMove
13402         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13403       snprintf(string, MSG_SIZ, partCommandString,
13404                appData.debugMode ? " -v" : "", appData.cmailGameName);
13405         commandOutput = popen(string, "r");
13406
13407         if (commandOutput == NULL) {
13408             DisplayError(_("Failed to invoke cmail"), 0);
13409         } else {
13410             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13411                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13412             }
13413             if (nBuffers > 1) {
13414                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13415                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13416                 nBytes = MSG_SIZ - 1;
13417             } else {
13418                 (void) memcpy(msg, buffer, nBytes);
13419             }
13420             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13421
13422             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13423                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13424
13425                 archived = TRUE;
13426                 for (i = 0; i < nCmailGames; i ++) {
13427                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13428                         archived = FALSE;
13429                     }
13430                 }
13431                 if (   archived
13432                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13433                         != NULL)) {
13434                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13435                            arcDir,
13436                            appData.cmailGameName,
13437                            gameInfo.date);
13438                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13439                     cmailMsgLoaded = FALSE;
13440                 }
13441             }
13442
13443             DisplayInformation(msg);
13444             pclose(commandOutput);
13445         }
13446     } else {
13447         if ((*cmailMsg) != '\0') {
13448             DisplayInformation(cmailMsg);
13449         }
13450     }
13451
13452     return;
13453 #endif /* !WIN32 */
13454 }
13455
13456 char *
13457 CmailMsg ()
13458 {
13459 #if WIN32
13460     return NULL;
13461 #else
13462     int  prependComma = 0;
13463     char number[5];
13464     char string[MSG_SIZ];       /* Space for game-list */
13465     int  i;
13466
13467     if (!cmailMsgLoaded) return "";
13468
13469     if (cmailMailedMove) {
13470       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13471     } else {
13472         /* Create a list of games left */
13473       snprintf(string, MSG_SIZ, "[");
13474         for (i = 0; i < nCmailGames; i ++) {
13475             if (! (   cmailMoveRegistered[i]
13476                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13477                 if (prependComma) {
13478                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13479                 } else {
13480                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13481                     prependComma = 1;
13482                 }
13483
13484                 strcat(string, number);
13485             }
13486         }
13487         strcat(string, "]");
13488
13489         if (nCmailMovesRegistered + nCmailResults == 0) {
13490             switch (nCmailGames) {
13491               case 1:
13492                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13493                 break;
13494
13495               case 2:
13496                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13497                 break;
13498
13499               default:
13500                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13501                          nCmailGames);
13502                 break;
13503             }
13504         } else {
13505             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13506               case 1:
13507                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13508                          string);
13509                 break;
13510
13511               case 0:
13512                 if (nCmailResults == nCmailGames) {
13513                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13514                 } else {
13515                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13516                 }
13517                 break;
13518
13519               default:
13520                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13521                          string);
13522             }
13523         }
13524     }
13525     return cmailMsg;
13526 #endif /* WIN32 */
13527 }
13528
13529 void
13530 ResetGameEvent ()
13531 {
13532     if (gameMode == Training)
13533       SetTrainingModeOff();
13534
13535     Reset(TRUE, TRUE);
13536     cmailMsgLoaded = FALSE;
13537     if (appData.icsActive) {
13538       SendToICS(ics_prefix);
13539       SendToICS("refresh\n");
13540     }
13541 }
13542
13543 void
13544 ExitEvent (int status)
13545 {
13546     exiting++;
13547     if (exiting > 2) {
13548       /* Give up on clean exit */
13549       exit(status);
13550     }
13551     if (exiting > 1) {
13552       /* Keep trying for clean exit */
13553       return;
13554     }
13555
13556     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13557
13558     if (telnetISR != NULL) {
13559       RemoveInputSource(telnetISR);
13560     }
13561     if (icsPR != NoProc) {
13562       DestroyChildProcess(icsPR, TRUE);
13563     }
13564
13565     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13566     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13567
13568     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13569     /* make sure this other one finishes before killing it!                  */
13570     if(endingGame) { int count = 0;
13571         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13572         while(endingGame && count++ < 10) DoSleep(1);
13573         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13574     }
13575
13576     /* Kill off chess programs */
13577     if (first.pr != NoProc) {
13578         ExitAnalyzeMode();
13579
13580         DoSleep( appData.delayBeforeQuit );
13581         SendToProgram("quit\n", &first);
13582         DoSleep( appData.delayAfterQuit );
13583         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13584     }
13585     if (second.pr != NoProc) {
13586         DoSleep( appData.delayBeforeQuit );
13587         SendToProgram("quit\n", &second);
13588         DoSleep( appData.delayAfterQuit );
13589         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13590     }
13591     if (first.isr != NULL) {
13592         RemoveInputSource(first.isr);
13593     }
13594     if (second.isr != NULL) {
13595         RemoveInputSource(second.isr);
13596     }
13597
13598     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13599     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13600
13601     ShutDownFrontEnd();
13602     exit(status);
13603 }
13604
13605 void
13606 PauseEngine (ChessProgramState *cps)
13607 {
13608     SendToProgram("pause\n", cps);
13609     cps->pause = 2;
13610 }
13611
13612 void
13613 UnPauseEngine (ChessProgramState *cps)
13614 {
13615     SendToProgram("resume\n", cps);
13616     cps->pause = 1;
13617 }
13618
13619 void
13620 PauseEvent ()
13621 {
13622     if (appData.debugMode)
13623         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13624     if (pausing) {
13625         pausing = FALSE;
13626         ModeHighlight();
13627         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13628             StartClocks();
13629             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13630                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13631                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13632             }
13633             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13634             HandleMachineMove(stashedInputMove, stalledEngine);
13635             stalledEngine = NULL;
13636             return;
13637         }
13638         if (gameMode == MachinePlaysWhite ||
13639             gameMode == TwoMachinesPlay   ||
13640             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13641             if(first.pause)  UnPauseEngine(&first);
13642             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13643             if(second.pause) UnPauseEngine(&second);
13644             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13645             StartClocks();
13646         } else {
13647             DisplayBothClocks();
13648         }
13649         if (gameMode == PlayFromGameFile) {
13650             if (appData.timeDelay >= 0)
13651                 AutoPlayGameLoop();
13652         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13653             Reset(FALSE, TRUE);
13654             SendToICS(ics_prefix);
13655             SendToICS("refresh\n");
13656         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13657             ForwardInner(forwardMostMove);
13658         }
13659         pauseExamInvalid = FALSE;
13660     } else {
13661         switch (gameMode) {
13662           default:
13663             return;
13664           case IcsExamining:
13665             pauseExamForwardMostMove = forwardMostMove;
13666             pauseExamInvalid = FALSE;
13667             /* fall through */
13668           case IcsObserving:
13669           case IcsPlayingWhite:
13670           case IcsPlayingBlack:
13671             pausing = TRUE;
13672             ModeHighlight();
13673             return;
13674           case PlayFromGameFile:
13675             (void) StopLoadGameTimer();
13676             pausing = TRUE;
13677             ModeHighlight();
13678             break;
13679           case BeginningOfGame:
13680             if (appData.icsActive) return;
13681             /* else fall through */
13682           case MachinePlaysWhite:
13683           case MachinePlaysBlack:
13684           case TwoMachinesPlay:
13685             if (forwardMostMove == 0)
13686               return;           /* don't pause if no one has moved */
13687             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13688                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13689                 if(onMove->pause) {           // thinking engine can be paused
13690                     PauseEngine(onMove);      // do it
13691                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13692                         PauseEngine(onMove->other);
13693                     else
13694                         SendToProgram("easy\n", onMove->other);
13695                     StopClocks();
13696                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13697             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13698                 if(first.pause) {
13699                     PauseEngine(&first);
13700                     StopClocks();
13701                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13702             } else { // human on move, pause pondering by either method
13703                 if(first.pause)
13704                     PauseEngine(&first);
13705                 else if(appData.ponderNextMove)
13706                     SendToProgram("easy\n", &first);
13707                 StopClocks();
13708             }
13709             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13710           case AnalyzeMode:
13711             pausing = TRUE;
13712             ModeHighlight();
13713             break;
13714         }
13715     }
13716 }
13717
13718 void
13719 EditCommentEvent ()
13720 {
13721     char title[MSG_SIZ];
13722
13723     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13724       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13725     } else {
13726       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13727                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13728                parseList[currentMove - 1]);
13729     }
13730
13731     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13732 }
13733
13734
13735 void
13736 EditTagsEvent ()
13737 {
13738     char *tags = PGNTags(&gameInfo);
13739     bookUp = FALSE;
13740     EditTagsPopUp(tags, NULL);
13741     free(tags);
13742 }
13743
13744 void
13745 ToggleSecond ()
13746 {
13747   if(second.analyzing) {
13748     SendToProgram("exit\n", &second);
13749     second.analyzing = FALSE;
13750   } else {
13751     if (second.pr == NoProc) StartChessProgram(&second);
13752     InitChessProgram(&second, FALSE);
13753     FeedMovesToProgram(&second, currentMove);
13754
13755     SendToProgram("analyze\n", &second);
13756     second.analyzing = TRUE;
13757   }
13758 }
13759
13760 /* Toggle ShowThinking */
13761 void
13762 ToggleShowThinking()
13763 {
13764   appData.showThinking = !appData.showThinking;
13765   ShowThinkingEvent();
13766 }
13767
13768 int
13769 AnalyzeModeEvent ()
13770 {
13771     char buf[MSG_SIZ];
13772
13773     if (!first.analysisSupport) {
13774       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13775       DisplayError(buf, 0);
13776       return 0;
13777     }
13778     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13779     if (appData.icsActive) {
13780         if (gameMode != IcsObserving) {
13781           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13782             DisplayError(buf, 0);
13783             /* secure check */
13784             if (appData.icsEngineAnalyze) {
13785                 if (appData.debugMode)
13786                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13787                 ExitAnalyzeMode();
13788                 ModeHighlight();
13789             }
13790             return 0;
13791         }
13792         /* if enable, user wants to disable icsEngineAnalyze */
13793         if (appData.icsEngineAnalyze) {
13794                 ExitAnalyzeMode();
13795                 ModeHighlight();
13796                 return 0;
13797         }
13798         appData.icsEngineAnalyze = TRUE;
13799         if (appData.debugMode)
13800             fprintf(debugFP, "ICS engine analyze starting... \n");
13801     }
13802
13803     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13804     if (appData.noChessProgram || gameMode == AnalyzeMode)
13805       return 0;
13806
13807     if (gameMode != AnalyzeFile) {
13808         if (!appData.icsEngineAnalyze) {
13809                EditGameEvent();
13810                if (gameMode != EditGame) return 0;
13811         }
13812         if (!appData.showThinking) ToggleShowThinking();
13813         ResurrectChessProgram();
13814         SendToProgram("analyze\n", &first);
13815         first.analyzing = TRUE;
13816         /*first.maybeThinking = TRUE;*/
13817         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13818         EngineOutputPopUp();
13819     }
13820     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13821     pausing = FALSE;
13822     ModeHighlight();
13823     SetGameInfo();
13824
13825     StartAnalysisClock();
13826     GetTimeMark(&lastNodeCountTime);
13827     lastNodeCount = 0;
13828     return 1;
13829 }
13830
13831 void
13832 AnalyzeFileEvent ()
13833 {
13834     if (appData.noChessProgram || gameMode == AnalyzeFile)
13835       return;
13836
13837     if (!first.analysisSupport) {
13838       char buf[MSG_SIZ];
13839       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13840       DisplayError(buf, 0);
13841       return;
13842     }
13843
13844     if (gameMode != AnalyzeMode) {
13845         keepInfo = 1; // mere annotating should not alter PGN tags
13846         EditGameEvent();
13847         keepInfo = 0;
13848         if (gameMode != EditGame) return;
13849         if (!appData.showThinking) ToggleShowThinking();
13850         ResurrectChessProgram();
13851         SendToProgram("analyze\n", &first);
13852         first.analyzing = TRUE;
13853         /*first.maybeThinking = TRUE;*/
13854         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13855         EngineOutputPopUp();
13856     }
13857     gameMode = AnalyzeFile;
13858     pausing = FALSE;
13859     ModeHighlight();
13860
13861     StartAnalysisClock();
13862     GetTimeMark(&lastNodeCountTime);
13863     lastNodeCount = 0;
13864     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13865     AnalysisPeriodicEvent(1);
13866 }
13867
13868 void
13869 MachineWhiteEvent ()
13870 {
13871     char buf[MSG_SIZ];
13872     char *bookHit = NULL;
13873
13874     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13875       return;
13876
13877
13878     if (gameMode == PlayFromGameFile ||
13879         gameMode == TwoMachinesPlay  ||
13880         gameMode == Training         ||
13881         gameMode == AnalyzeMode      ||
13882         gameMode == EndOfGame)
13883         EditGameEvent();
13884
13885     if (gameMode == EditPosition)
13886         EditPositionDone(TRUE);
13887
13888     if (!WhiteOnMove(currentMove)) {
13889         DisplayError(_("It is not White's turn"), 0);
13890         return;
13891     }
13892
13893     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13894       ExitAnalyzeMode();
13895
13896     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13897         gameMode == AnalyzeFile)
13898         TruncateGame();
13899
13900     ResurrectChessProgram();    /* in case it isn't running */
13901     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13902         gameMode = MachinePlaysWhite;
13903         ResetClocks();
13904     } else
13905     gameMode = MachinePlaysWhite;
13906     pausing = FALSE;
13907     ModeHighlight();
13908     SetGameInfo();
13909     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13910     DisplayTitle(buf);
13911     if (first.sendName) {
13912       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13913       SendToProgram(buf, &first);
13914     }
13915     if (first.sendTime) {
13916       if (first.useColors) {
13917         SendToProgram("black\n", &first); /*gnu kludge*/
13918       }
13919       SendTimeRemaining(&first, TRUE);
13920     }
13921     if (first.useColors) {
13922       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13923     }
13924     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13925     SetMachineThinkingEnables();
13926     first.maybeThinking = TRUE;
13927     StartClocks();
13928     firstMove = FALSE;
13929
13930     if (appData.autoFlipView && !flipView) {
13931       flipView = !flipView;
13932       DrawPosition(FALSE, NULL);
13933       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13934     }
13935
13936     if(bookHit) { // [HGM] book: simulate book reply
13937         static char bookMove[MSG_SIZ]; // a bit generous?
13938
13939         programStats.nodes = programStats.depth = programStats.time =
13940         programStats.score = programStats.got_only_move = 0;
13941         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13942
13943         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13944         strcat(bookMove, bookHit);
13945         HandleMachineMove(bookMove, &first);
13946     }
13947 }
13948
13949 void
13950 MachineBlackEvent ()
13951 {
13952   char buf[MSG_SIZ];
13953   char *bookHit = NULL;
13954
13955     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13956         return;
13957
13958
13959     if (gameMode == PlayFromGameFile ||
13960         gameMode == TwoMachinesPlay  ||
13961         gameMode == Training         ||
13962         gameMode == AnalyzeMode      ||
13963         gameMode == EndOfGame)
13964         EditGameEvent();
13965
13966     if (gameMode == EditPosition)
13967         EditPositionDone(TRUE);
13968
13969     if (WhiteOnMove(currentMove)) {
13970         DisplayError(_("It is not Black's turn"), 0);
13971         return;
13972     }
13973
13974     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13975       ExitAnalyzeMode();
13976
13977     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13978         gameMode == AnalyzeFile)
13979         TruncateGame();
13980
13981     ResurrectChessProgram();    /* in case it isn't running */
13982     gameMode = MachinePlaysBlack;
13983     pausing = FALSE;
13984     ModeHighlight();
13985     SetGameInfo();
13986     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13987     DisplayTitle(buf);
13988     if (first.sendName) {
13989       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13990       SendToProgram(buf, &first);
13991     }
13992     if (first.sendTime) {
13993       if (first.useColors) {
13994         SendToProgram("white\n", &first); /*gnu kludge*/
13995       }
13996       SendTimeRemaining(&first, FALSE);
13997     }
13998     if (first.useColors) {
13999       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14000     }
14001     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14002     SetMachineThinkingEnables();
14003     first.maybeThinking = TRUE;
14004     StartClocks();
14005
14006     if (appData.autoFlipView && flipView) {
14007       flipView = !flipView;
14008       DrawPosition(FALSE, NULL);
14009       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14010     }
14011     if(bookHit) { // [HGM] book: simulate book reply
14012         static char bookMove[MSG_SIZ]; // a bit generous?
14013
14014         programStats.nodes = programStats.depth = programStats.time =
14015         programStats.score = programStats.got_only_move = 0;
14016         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14017
14018         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14019         strcat(bookMove, bookHit);
14020         HandleMachineMove(bookMove, &first);
14021     }
14022 }
14023
14024
14025 void
14026 DisplayTwoMachinesTitle ()
14027 {
14028     char buf[MSG_SIZ];
14029     if (appData.matchGames > 0) {
14030         if(appData.tourneyFile[0]) {
14031           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14032                    gameInfo.white, _("vs."), gameInfo.black,
14033                    nextGame+1, appData.matchGames+1,
14034                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14035         } else
14036         if (first.twoMachinesColor[0] == 'w') {
14037           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14038                    gameInfo.white, _("vs."),  gameInfo.black,
14039                    first.matchWins, second.matchWins,
14040                    matchGame - 1 - (first.matchWins + second.matchWins));
14041         } else {
14042           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14043                    gameInfo.white, _("vs."), gameInfo.black,
14044                    second.matchWins, first.matchWins,
14045                    matchGame - 1 - (first.matchWins + second.matchWins));
14046         }
14047     } else {
14048       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14049     }
14050     DisplayTitle(buf);
14051 }
14052
14053 void
14054 SettingsMenuIfReady ()
14055 {
14056   if (second.lastPing != second.lastPong) {
14057     DisplayMessage("", _("Waiting for second chess program"));
14058     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14059     return;
14060   }
14061   ThawUI();
14062   DisplayMessage("", "");
14063   SettingsPopUp(&second);
14064 }
14065
14066 int
14067 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14068 {
14069     char buf[MSG_SIZ];
14070     if (cps->pr == NoProc) {
14071         StartChessProgram(cps);
14072         if (cps->protocolVersion == 1) {
14073           retry();
14074           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14075         } else {
14076           /* kludge: allow timeout for initial "feature" command */
14077           if(retry != TwoMachinesEventIfReady) FreezeUI();
14078           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14079           DisplayMessage("", buf);
14080           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14081         }
14082         return 1;
14083     }
14084     return 0;
14085 }
14086
14087 void
14088 TwoMachinesEvent P((void))
14089 {
14090     int i;
14091     char buf[MSG_SIZ];
14092     ChessProgramState *onmove;
14093     char *bookHit = NULL;
14094     static int stalling = 0;
14095     TimeMark now;
14096     long wait;
14097
14098     if (appData.noChessProgram) return;
14099
14100     switch (gameMode) {
14101       case TwoMachinesPlay:
14102         return;
14103       case MachinePlaysWhite:
14104       case MachinePlaysBlack:
14105         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14106             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14107             return;
14108         }
14109         /* fall through */
14110       case BeginningOfGame:
14111       case PlayFromGameFile:
14112       case EndOfGame:
14113         EditGameEvent();
14114         if (gameMode != EditGame) return;
14115         break;
14116       case EditPosition:
14117         EditPositionDone(TRUE);
14118         break;
14119       case AnalyzeMode:
14120       case AnalyzeFile:
14121         ExitAnalyzeMode();
14122         break;
14123       case EditGame:
14124       default:
14125         break;
14126     }
14127
14128 //    forwardMostMove = currentMove;
14129     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14130     startingEngine = TRUE;
14131
14132     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14133
14134     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14135     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14136       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14137       return;
14138     }
14139     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14140
14141     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14142         startingEngine = FALSE;
14143         DisplayError("second engine does not play this", 0);
14144         return;
14145     }
14146
14147     if(!stalling) {
14148       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14149       SendToProgram("force\n", &second);
14150       stalling = 1;
14151       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14152       return;
14153     }
14154     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14155     if(appData.matchPause>10000 || appData.matchPause<10)
14156                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14157     wait = SubtractTimeMarks(&now, &pauseStart);
14158     if(wait < appData.matchPause) {
14159         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14160         return;
14161     }
14162     // we are now committed to starting the game
14163     stalling = 0;
14164     DisplayMessage("", "");
14165     if (startedFromSetupPosition) {
14166         SendBoard(&second, backwardMostMove);
14167     if (appData.debugMode) {
14168         fprintf(debugFP, "Two Machines\n");
14169     }
14170     }
14171     for (i = backwardMostMove; i < forwardMostMove; i++) {
14172         SendMoveToProgram(i, &second);
14173     }
14174
14175     gameMode = TwoMachinesPlay;
14176     pausing = startingEngine = FALSE;
14177     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14178     SetGameInfo();
14179     DisplayTwoMachinesTitle();
14180     firstMove = TRUE;
14181     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14182         onmove = &first;
14183     } else {
14184         onmove = &second;
14185     }
14186     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14187     SendToProgram(first.computerString, &first);
14188     if (first.sendName) {
14189       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14190       SendToProgram(buf, &first);
14191     }
14192     SendToProgram(second.computerString, &second);
14193     if (second.sendName) {
14194       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14195       SendToProgram(buf, &second);
14196     }
14197
14198     ResetClocks();
14199     if (!first.sendTime || !second.sendTime) {
14200         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14201         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14202     }
14203     if (onmove->sendTime) {
14204       if (onmove->useColors) {
14205         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14206       }
14207       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14208     }
14209     if (onmove->useColors) {
14210       SendToProgram(onmove->twoMachinesColor, onmove);
14211     }
14212     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14213 //    SendToProgram("go\n", onmove);
14214     onmove->maybeThinking = TRUE;
14215     SetMachineThinkingEnables();
14216
14217     StartClocks();
14218
14219     if(bookHit) { // [HGM] book: simulate book reply
14220         static char bookMove[MSG_SIZ]; // a bit generous?
14221
14222         programStats.nodes = programStats.depth = programStats.time =
14223         programStats.score = programStats.got_only_move = 0;
14224         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14225
14226         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14227         strcat(bookMove, bookHit);
14228         savedMessage = bookMove; // args for deferred call
14229         savedState = onmove;
14230         ScheduleDelayedEvent(DeferredBookMove, 1);
14231     }
14232 }
14233
14234 void
14235 TrainingEvent ()
14236 {
14237     if (gameMode == Training) {
14238       SetTrainingModeOff();
14239       gameMode = PlayFromGameFile;
14240       DisplayMessage("", _("Training mode off"));
14241     } else {
14242       gameMode = Training;
14243       animateTraining = appData.animate;
14244
14245       /* make sure we are not already at the end of the game */
14246       if (currentMove < forwardMostMove) {
14247         SetTrainingModeOn();
14248         DisplayMessage("", _("Training mode on"));
14249       } else {
14250         gameMode = PlayFromGameFile;
14251         DisplayError(_("Already at end of game"), 0);
14252       }
14253     }
14254     ModeHighlight();
14255 }
14256
14257 void
14258 IcsClientEvent ()
14259 {
14260     if (!appData.icsActive) return;
14261     switch (gameMode) {
14262       case IcsPlayingWhite:
14263       case IcsPlayingBlack:
14264       case IcsObserving:
14265       case IcsIdle:
14266       case BeginningOfGame:
14267       case IcsExamining:
14268         return;
14269
14270       case EditGame:
14271         break;
14272
14273       case EditPosition:
14274         EditPositionDone(TRUE);
14275         break;
14276
14277       case AnalyzeMode:
14278       case AnalyzeFile:
14279         ExitAnalyzeMode();
14280         break;
14281
14282       default:
14283         EditGameEvent();
14284         break;
14285     }
14286
14287     gameMode = IcsIdle;
14288     ModeHighlight();
14289     return;
14290 }
14291
14292 void
14293 EditGameEvent ()
14294 {
14295     int i;
14296
14297     switch (gameMode) {
14298       case Training:
14299         SetTrainingModeOff();
14300         break;
14301       case MachinePlaysWhite:
14302       case MachinePlaysBlack:
14303       case BeginningOfGame:
14304         SendToProgram("force\n", &first);
14305         SetUserThinkingEnables();
14306         break;
14307       case PlayFromGameFile:
14308         (void) StopLoadGameTimer();
14309         if (gameFileFP != NULL) {
14310             gameFileFP = NULL;
14311         }
14312         break;
14313       case EditPosition:
14314         EditPositionDone(TRUE);
14315         break;
14316       case AnalyzeMode:
14317       case AnalyzeFile:
14318         ExitAnalyzeMode();
14319         SendToProgram("force\n", &first);
14320         break;
14321       case TwoMachinesPlay:
14322         GameEnds(EndOfFile, NULL, GE_PLAYER);
14323         ResurrectChessProgram();
14324         SetUserThinkingEnables();
14325         break;
14326       case EndOfGame:
14327         ResurrectChessProgram();
14328         break;
14329       case IcsPlayingBlack:
14330       case IcsPlayingWhite:
14331         DisplayError(_("Warning: You are still playing a game"), 0);
14332         break;
14333       case IcsObserving:
14334         DisplayError(_("Warning: You are still observing a game"), 0);
14335         break;
14336       case IcsExamining:
14337         DisplayError(_("Warning: You are still examining a game"), 0);
14338         break;
14339       case IcsIdle:
14340         break;
14341       case EditGame:
14342       default:
14343         return;
14344     }
14345
14346     pausing = FALSE;
14347     StopClocks();
14348     first.offeredDraw = second.offeredDraw = 0;
14349
14350     if (gameMode == PlayFromGameFile) {
14351         whiteTimeRemaining = timeRemaining[0][currentMove];
14352         blackTimeRemaining = timeRemaining[1][currentMove];
14353         DisplayTitle("");
14354     }
14355
14356     if (gameMode == MachinePlaysWhite ||
14357         gameMode == MachinePlaysBlack ||
14358         gameMode == TwoMachinesPlay ||
14359         gameMode == EndOfGame) {
14360         i = forwardMostMove;
14361         while (i > currentMove) {
14362             SendToProgram("undo\n", &first);
14363             i--;
14364         }
14365         if(!adjustedClock) {
14366         whiteTimeRemaining = timeRemaining[0][currentMove];
14367         blackTimeRemaining = timeRemaining[1][currentMove];
14368         DisplayBothClocks();
14369         }
14370         if (whiteFlag || blackFlag) {
14371             whiteFlag = blackFlag = 0;
14372         }
14373         DisplayTitle("");
14374     }
14375
14376     gameMode = EditGame;
14377     ModeHighlight();
14378     SetGameInfo();
14379 }
14380
14381
14382 void
14383 EditPositionEvent ()
14384 {
14385     if (gameMode == EditPosition) {
14386         EditGameEvent();
14387         return;
14388     }
14389
14390     EditGameEvent();
14391     if (gameMode != EditGame) return;
14392
14393     gameMode = EditPosition;
14394     ModeHighlight();
14395     SetGameInfo();
14396     if (currentMove > 0)
14397       CopyBoard(boards[0], boards[currentMove]);
14398
14399     blackPlaysFirst = !WhiteOnMove(currentMove);
14400     ResetClocks();
14401     currentMove = forwardMostMove = backwardMostMove = 0;
14402     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14403     DisplayMove(-1);
14404     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14405 }
14406
14407 void
14408 ExitAnalyzeMode ()
14409 {
14410     /* [DM] icsEngineAnalyze - possible call from other functions */
14411     if (appData.icsEngineAnalyze) {
14412         appData.icsEngineAnalyze = FALSE;
14413
14414         DisplayMessage("",_("Close ICS engine analyze..."));
14415     }
14416     if (first.analysisSupport && first.analyzing) {
14417       SendToBoth("exit\n");
14418       first.analyzing = second.analyzing = FALSE;
14419     }
14420     thinkOutput[0] = NULLCHAR;
14421 }
14422
14423 void
14424 EditPositionDone (Boolean fakeRights)
14425 {
14426     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14427
14428     startedFromSetupPosition = TRUE;
14429     InitChessProgram(&first, FALSE);
14430     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14431       boards[0][EP_STATUS] = EP_NONE;
14432       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14433       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14434         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14435         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14436       } else boards[0][CASTLING][2] = NoRights;
14437       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14438         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14439         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14440       } else boards[0][CASTLING][5] = NoRights;
14441       if(gameInfo.variant == VariantSChess) {
14442         int i;
14443         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14444           boards[0][VIRGIN][i] = 0;
14445           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14446           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14447         }
14448       }
14449     }
14450     SendToProgram("force\n", &first);
14451     if (blackPlaysFirst) {
14452         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14453         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14454         currentMove = forwardMostMove = backwardMostMove = 1;
14455         CopyBoard(boards[1], boards[0]);
14456     } else {
14457         currentMove = forwardMostMove = backwardMostMove = 0;
14458     }
14459     SendBoard(&first, forwardMostMove);
14460     if (appData.debugMode) {
14461         fprintf(debugFP, "EditPosDone\n");
14462     }
14463     DisplayTitle("");
14464     DisplayMessage("", "");
14465     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14466     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14467     gameMode = EditGame;
14468     ModeHighlight();
14469     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14470     ClearHighlights(); /* [AS] */
14471 }
14472
14473 /* Pause for `ms' milliseconds */
14474 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14475 void
14476 TimeDelay (long ms)
14477 {
14478     TimeMark m1, m2;
14479
14480     GetTimeMark(&m1);
14481     do {
14482         GetTimeMark(&m2);
14483     } while (SubtractTimeMarks(&m2, &m1) < ms);
14484 }
14485
14486 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14487 void
14488 SendMultiLineToICS (char *buf)
14489 {
14490     char temp[MSG_SIZ+1], *p;
14491     int len;
14492
14493     len = strlen(buf);
14494     if (len > MSG_SIZ)
14495       len = MSG_SIZ;
14496
14497     strncpy(temp, buf, len);
14498     temp[len] = 0;
14499
14500     p = temp;
14501     while (*p) {
14502         if (*p == '\n' || *p == '\r')
14503           *p = ' ';
14504         ++p;
14505     }
14506
14507     strcat(temp, "\n");
14508     SendToICS(temp);
14509     SendToPlayer(temp, strlen(temp));
14510 }
14511
14512 void
14513 SetWhiteToPlayEvent ()
14514 {
14515     if (gameMode == EditPosition) {
14516         blackPlaysFirst = FALSE;
14517         DisplayBothClocks();    /* works because currentMove is 0 */
14518     } else if (gameMode == IcsExamining) {
14519         SendToICS(ics_prefix);
14520         SendToICS("tomove white\n");
14521     }
14522 }
14523
14524 void
14525 SetBlackToPlayEvent ()
14526 {
14527     if (gameMode == EditPosition) {
14528         blackPlaysFirst = TRUE;
14529         currentMove = 1;        /* kludge */
14530         DisplayBothClocks();
14531         currentMove = 0;
14532     } else if (gameMode == IcsExamining) {
14533         SendToICS(ics_prefix);
14534         SendToICS("tomove black\n");
14535     }
14536 }
14537
14538 void
14539 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14540 {
14541     char buf[MSG_SIZ];
14542     ChessSquare piece = boards[0][y][x];
14543
14544     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14545
14546     switch (selection) {
14547       case ClearBoard:
14548         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14549             SendToICS(ics_prefix);
14550             SendToICS("bsetup clear\n");
14551         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14552             SendToICS(ics_prefix);
14553             SendToICS("clearboard\n");
14554         } else {
14555             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14556                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14557                 for (y = 0; y < BOARD_HEIGHT; y++) {
14558                     if (gameMode == IcsExamining) {
14559                         if (boards[currentMove][y][x] != EmptySquare) {
14560                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14561                                     AAA + x, ONE + y);
14562                             SendToICS(buf);
14563                         }
14564                     } else {
14565                         boards[0][y][x] = p;
14566                     }
14567                 }
14568             }
14569         }
14570         if (gameMode == EditPosition) {
14571             DrawPosition(FALSE, boards[0]);
14572         }
14573         break;
14574
14575       case WhitePlay:
14576         SetWhiteToPlayEvent();
14577         break;
14578
14579       case BlackPlay:
14580         SetBlackToPlayEvent();
14581         break;
14582
14583       case EmptySquare:
14584         if (gameMode == IcsExamining) {
14585             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14586             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14587             SendToICS(buf);
14588         } else {
14589             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14590                 if(x == BOARD_LEFT-2) {
14591                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14592                     boards[0][y][1] = 0;
14593                 } else
14594                 if(x == BOARD_RGHT+1) {
14595                     if(y >= gameInfo.holdingsSize) break;
14596                     boards[0][y][BOARD_WIDTH-2] = 0;
14597                 } else break;
14598             }
14599             boards[0][y][x] = EmptySquare;
14600             DrawPosition(FALSE, boards[0]);
14601         }
14602         break;
14603
14604       case PromotePiece:
14605         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14606            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14607             selection = (ChessSquare) (PROMOTED piece);
14608         } else if(piece == EmptySquare) selection = WhiteSilver;
14609         else selection = (ChessSquare)((int)piece - 1);
14610         goto defaultlabel;
14611
14612       case DemotePiece:
14613         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14614            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14615             selection = (ChessSquare) (DEMOTED piece);
14616         } else if(piece == EmptySquare) selection = BlackSilver;
14617         else selection = (ChessSquare)((int)piece + 1);
14618         goto defaultlabel;
14619
14620       case WhiteQueen:
14621       case BlackQueen:
14622         if(gameInfo.variant == VariantShatranj ||
14623            gameInfo.variant == VariantXiangqi  ||
14624            gameInfo.variant == VariantCourier  ||
14625            gameInfo.variant == VariantASEAN    ||
14626            gameInfo.variant == VariantMakruk     )
14627             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14628         goto defaultlabel;
14629
14630       case WhiteKing:
14631       case BlackKing:
14632         if(gameInfo.variant == VariantXiangqi)
14633             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14634         if(gameInfo.variant == VariantKnightmate)
14635             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14636       default:
14637         defaultlabel:
14638         if (gameMode == IcsExamining) {
14639             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14640             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14641                      PieceToChar(selection), AAA + x, ONE + y);
14642             SendToICS(buf);
14643         } else {
14644             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14645                 int n;
14646                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14647                     n = PieceToNumber(selection - BlackPawn);
14648                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14649                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14650                     boards[0][BOARD_HEIGHT-1-n][1]++;
14651                 } else
14652                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14653                     n = PieceToNumber(selection);
14654                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14655                     boards[0][n][BOARD_WIDTH-1] = selection;
14656                     boards[0][n][BOARD_WIDTH-2]++;
14657                 }
14658             } else
14659             boards[0][y][x] = selection;
14660             DrawPosition(TRUE, boards[0]);
14661             ClearHighlights();
14662             fromX = fromY = -1;
14663         }
14664         break;
14665     }
14666 }
14667
14668
14669 void
14670 DropMenuEvent (ChessSquare selection, int x, int y)
14671 {
14672     ChessMove moveType;
14673
14674     switch (gameMode) {
14675       case IcsPlayingWhite:
14676       case MachinePlaysBlack:
14677         if (!WhiteOnMove(currentMove)) {
14678             DisplayMoveError(_("It is Black's turn"));
14679             return;
14680         }
14681         moveType = WhiteDrop;
14682         break;
14683       case IcsPlayingBlack:
14684       case MachinePlaysWhite:
14685         if (WhiteOnMove(currentMove)) {
14686             DisplayMoveError(_("It is White's turn"));
14687             return;
14688         }
14689         moveType = BlackDrop;
14690         break;
14691       case EditGame:
14692         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14693         break;
14694       default:
14695         return;
14696     }
14697
14698     if (moveType == BlackDrop && selection < BlackPawn) {
14699       selection = (ChessSquare) ((int) selection
14700                                  + (int) BlackPawn - (int) WhitePawn);
14701     }
14702     if (boards[currentMove][y][x] != EmptySquare) {
14703         DisplayMoveError(_("That square is occupied"));
14704         return;
14705     }
14706
14707     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14708 }
14709
14710 void
14711 AcceptEvent ()
14712 {
14713     /* Accept a pending offer of any kind from opponent */
14714
14715     if (appData.icsActive) {
14716         SendToICS(ics_prefix);
14717         SendToICS("accept\n");
14718     } else if (cmailMsgLoaded) {
14719         if (currentMove == cmailOldMove &&
14720             commentList[cmailOldMove] != NULL &&
14721             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14722                    "Black offers a draw" : "White offers a draw")) {
14723             TruncateGame();
14724             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14726         } else {
14727             DisplayError(_("There is no pending offer on this move"), 0);
14728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14729         }
14730     } else {
14731         /* Not used for offers from chess program */
14732     }
14733 }
14734
14735 void
14736 DeclineEvent ()
14737 {
14738     /* Decline a pending offer of any kind from opponent */
14739
14740     if (appData.icsActive) {
14741         SendToICS(ics_prefix);
14742         SendToICS("decline\n");
14743     } else if (cmailMsgLoaded) {
14744         if (currentMove == cmailOldMove &&
14745             commentList[cmailOldMove] != NULL &&
14746             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14747                    "Black offers a draw" : "White offers a draw")) {
14748 #ifdef NOTDEF
14749             AppendComment(cmailOldMove, "Draw declined", TRUE);
14750             DisplayComment(cmailOldMove - 1, "Draw declined");
14751 #endif /*NOTDEF*/
14752         } else {
14753             DisplayError(_("There is no pending offer on this move"), 0);
14754         }
14755     } else {
14756         /* Not used for offers from chess program */
14757     }
14758 }
14759
14760 void
14761 RematchEvent ()
14762 {
14763     /* Issue ICS rematch command */
14764     if (appData.icsActive) {
14765         SendToICS(ics_prefix);
14766         SendToICS("rematch\n");
14767     }
14768 }
14769
14770 void
14771 CallFlagEvent ()
14772 {
14773     /* Call your opponent's flag (claim a win on time) */
14774     if (appData.icsActive) {
14775         SendToICS(ics_prefix);
14776         SendToICS("flag\n");
14777     } else {
14778         switch (gameMode) {
14779           default:
14780             return;
14781           case MachinePlaysWhite:
14782             if (whiteFlag) {
14783                 if (blackFlag)
14784                   GameEnds(GameIsDrawn, "Both players ran out of time",
14785                            GE_PLAYER);
14786                 else
14787                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14788             } else {
14789                 DisplayError(_("Your opponent is not out of time"), 0);
14790             }
14791             break;
14792           case MachinePlaysBlack:
14793             if (blackFlag) {
14794                 if (whiteFlag)
14795                   GameEnds(GameIsDrawn, "Both players ran out of time",
14796                            GE_PLAYER);
14797                 else
14798                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14799             } else {
14800                 DisplayError(_("Your opponent is not out of time"), 0);
14801             }
14802             break;
14803         }
14804     }
14805 }
14806
14807 void
14808 ClockClick (int which)
14809 {       // [HGM] code moved to back-end from winboard.c
14810         if(which) { // black clock
14811           if (gameMode == EditPosition || gameMode == IcsExamining) {
14812             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14813             SetBlackToPlayEvent();
14814           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14815           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14816           } else if (shiftKey) {
14817             AdjustClock(which, -1);
14818           } else if (gameMode == IcsPlayingWhite ||
14819                      gameMode == MachinePlaysBlack) {
14820             CallFlagEvent();
14821           }
14822         } else { // white clock
14823           if (gameMode == EditPosition || gameMode == IcsExamining) {
14824             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14825             SetWhiteToPlayEvent();
14826           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14827           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14828           } else if (shiftKey) {
14829             AdjustClock(which, -1);
14830           } else if (gameMode == IcsPlayingBlack ||
14831                    gameMode == MachinePlaysWhite) {
14832             CallFlagEvent();
14833           }
14834         }
14835 }
14836
14837 void
14838 DrawEvent ()
14839 {
14840     /* Offer draw or accept pending draw offer from opponent */
14841
14842     if (appData.icsActive) {
14843         /* Note: tournament rules require draw offers to be
14844            made after you make your move but before you punch
14845            your clock.  Currently ICS doesn't let you do that;
14846            instead, you immediately punch your clock after making
14847            a move, but you can offer a draw at any time. */
14848
14849         SendToICS(ics_prefix);
14850         SendToICS("draw\n");
14851         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14852     } else if (cmailMsgLoaded) {
14853         if (currentMove == cmailOldMove &&
14854             commentList[cmailOldMove] != NULL &&
14855             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14856                    "Black offers a draw" : "White offers a draw")) {
14857             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14858             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14859         } else if (currentMove == cmailOldMove + 1) {
14860             char *offer = WhiteOnMove(cmailOldMove) ?
14861               "White offers a draw" : "Black offers a draw";
14862             AppendComment(currentMove, offer, TRUE);
14863             DisplayComment(currentMove - 1, offer);
14864             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14865         } else {
14866             DisplayError(_("You must make your move before offering a draw"), 0);
14867             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14868         }
14869     } else if (first.offeredDraw) {
14870         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14871     } else {
14872         if (first.sendDrawOffers) {
14873             SendToProgram("draw\n", &first);
14874             userOfferedDraw = TRUE;
14875         }
14876     }
14877 }
14878
14879 void
14880 AdjournEvent ()
14881 {
14882     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14883
14884     if (appData.icsActive) {
14885         SendToICS(ics_prefix);
14886         SendToICS("adjourn\n");
14887     } else {
14888         /* Currently GNU Chess doesn't offer or accept Adjourns */
14889     }
14890 }
14891
14892
14893 void
14894 AbortEvent ()
14895 {
14896     /* Offer Abort or accept pending Abort offer from opponent */
14897
14898     if (appData.icsActive) {
14899         SendToICS(ics_prefix);
14900         SendToICS("abort\n");
14901     } else {
14902         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14903     }
14904 }
14905
14906 void
14907 ResignEvent ()
14908 {
14909     /* Resign.  You can do this even if it's not your turn. */
14910
14911     if (appData.icsActive) {
14912         SendToICS(ics_prefix);
14913         SendToICS("resign\n");
14914     } else {
14915         switch (gameMode) {
14916           case MachinePlaysWhite:
14917             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14918             break;
14919           case MachinePlaysBlack:
14920             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14921             break;
14922           case EditGame:
14923             if (cmailMsgLoaded) {
14924                 TruncateGame();
14925                 if (WhiteOnMove(cmailOldMove)) {
14926                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14927                 } else {
14928                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14929                 }
14930                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14931             }
14932             break;
14933           default:
14934             break;
14935         }
14936     }
14937 }
14938
14939
14940 void
14941 StopObservingEvent ()
14942 {
14943     /* Stop observing current games */
14944     SendToICS(ics_prefix);
14945     SendToICS("unobserve\n");
14946 }
14947
14948 void
14949 StopExaminingEvent ()
14950 {
14951     /* Stop observing current game */
14952     SendToICS(ics_prefix);
14953     SendToICS("unexamine\n");
14954 }
14955
14956 void
14957 ForwardInner (int target)
14958 {
14959     int limit; int oldSeekGraphUp = seekGraphUp;
14960
14961     if (appData.debugMode)
14962         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14963                 target, currentMove, forwardMostMove);
14964
14965     if (gameMode == EditPosition)
14966       return;
14967
14968     seekGraphUp = FALSE;
14969     MarkTargetSquares(1);
14970
14971     if (gameMode == PlayFromGameFile && !pausing)
14972       PauseEvent();
14973
14974     if (gameMode == IcsExamining && pausing)
14975       limit = pauseExamForwardMostMove;
14976     else
14977       limit = forwardMostMove;
14978
14979     if (target > limit) target = limit;
14980
14981     if (target > 0 && moveList[target - 1][0]) {
14982         int fromX, fromY, toX, toY;
14983         toX = moveList[target - 1][2] - AAA;
14984         toY = moveList[target - 1][3] - ONE;
14985         if (moveList[target - 1][1] == '@') {
14986             if (appData.highlightLastMove) {
14987                 SetHighlights(-1, -1, toX, toY);
14988             }
14989         } else {
14990             fromX = moveList[target - 1][0] - AAA;
14991             fromY = moveList[target - 1][1] - ONE;
14992             if (target == currentMove + 1) {
14993                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14994             }
14995             if (appData.highlightLastMove) {
14996                 SetHighlights(fromX, fromY, toX, toY);
14997             }
14998         }
14999     }
15000     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15001         gameMode == Training || gameMode == PlayFromGameFile ||
15002         gameMode == AnalyzeFile) {
15003         while (currentMove < target) {
15004             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15005             SendMoveToProgram(currentMove++, &first);
15006         }
15007     } else {
15008         currentMove = target;
15009     }
15010
15011     if (gameMode == EditGame || gameMode == EndOfGame) {
15012         whiteTimeRemaining = timeRemaining[0][currentMove];
15013         blackTimeRemaining = timeRemaining[1][currentMove];
15014     }
15015     DisplayBothClocks();
15016     DisplayMove(currentMove - 1);
15017     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15018     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15019     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15020         DisplayComment(currentMove - 1, commentList[currentMove]);
15021     }
15022     ClearMap(); // [HGM] exclude: invalidate map
15023 }
15024
15025
15026 void
15027 ForwardEvent ()
15028 {
15029     if (gameMode == IcsExamining && !pausing) {
15030         SendToICS(ics_prefix);
15031         SendToICS("forward\n");
15032     } else {
15033         ForwardInner(currentMove + 1);
15034     }
15035 }
15036
15037 void
15038 ToEndEvent ()
15039 {
15040     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15041         /* to optimze, we temporarily turn off analysis mode while we feed
15042          * the remaining moves to the engine. Otherwise we get analysis output
15043          * after each move.
15044          */
15045         if (first.analysisSupport) {
15046           SendToProgram("exit\nforce\n", &first);
15047           first.analyzing = FALSE;
15048         }
15049     }
15050
15051     if (gameMode == IcsExamining && !pausing) {
15052         SendToICS(ics_prefix);
15053         SendToICS("forward 999999\n");
15054     } else {
15055         ForwardInner(forwardMostMove);
15056     }
15057
15058     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15059         /* we have fed all the moves, so reactivate analysis mode */
15060         SendToProgram("analyze\n", &first);
15061         first.analyzing = TRUE;
15062         /*first.maybeThinking = TRUE;*/
15063         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15064     }
15065 }
15066
15067 void
15068 BackwardInner (int target)
15069 {
15070     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15071
15072     if (appData.debugMode)
15073         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15074                 target, currentMove, forwardMostMove);
15075
15076     if (gameMode == EditPosition) return;
15077     seekGraphUp = FALSE;
15078     MarkTargetSquares(1);
15079     if (currentMove <= backwardMostMove) {
15080         ClearHighlights();
15081         DrawPosition(full_redraw, boards[currentMove]);
15082         return;
15083     }
15084     if (gameMode == PlayFromGameFile && !pausing)
15085       PauseEvent();
15086
15087     if (moveList[target][0]) {
15088         int fromX, fromY, toX, toY;
15089         toX = moveList[target][2] - AAA;
15090         toY = moveList[target][3] - ONE;
15091         if (moveList[target][1] == '@') {
15092             if (appData.highlightLastMove) {
15093                 SetHighlights(-1, -1, toX, toY);
15094             }
15095         } else {
15096             fromX = moveList[target][0] - AAA;
15097             fromY = moveList[target][1] - ONE;
15098             if (target == currentMove - 1) {
15099                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15100             }
15101             if (appData.highlightLastMove) {
15102                 SetHighlights(fromX, fromY, toX, toY);
15103             }
15104         }
15105     }
15106     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15107         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15108         while (currentMove > target) {
15109             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15110                 // null move cannot be undone. Reload program with move history before it.
15111                 int i;
15112                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15113                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15114                 }
15115                 SendBoard(&first, i);
15116               if(second.analyzing) SendBoard(&second, i);
15117                 for(currentMove=i; currentMove<target; currentMove++) {
15118                     SendMoveToProgram(currentMove, &first);
15119                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15120                 }
15121                 break;
15122             }
15123             SendToBoth("undo\n");
15124             currentMove--;
15125         }
15126     } else {
15127         currentMove = target;
15128     }
15129
15130     if (gameMode == EditGame || gameMode == EndOfGame) {
15131         whiteTimeRemaining = timeRemaining[0][currentMove];
15132         blackTimeRemaining = timeRemaining[1][currentMove];
15133     }
15134     DisplayBothClocks();
15135     DisplayMove(currentMove - 1);
15136     DrawPosition(full_redraw, boards[currentMove]);
15137     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15138     // [HGM] PV info: routine tests if comment empty
15139     DisplayComment(currentMove - 1, commentList[currentMove]);
15140     ClearMap(); // [HGM] exclude: invalidate map
15141 }
15142
15143 void
15144 BackwardEvent ()
15145 {
15146     if (gameMode == IcsExamining && !pausing) {
15147         SendToICS(ics_prefix);
15148         SendToICS("backward\n");
15149     } else {
15150         BackwardInner(currentMove - 1);
15151     }
15152 }
15153
15154 void
15155 ToStartEvent ()
15156 {
15157     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15158         /* to optimize, we temporarily turn off analysis mode while we undo
15159          * all the moves. Otherwise we get analysis output after each undo.
15160          */
15161         if (first.analysisSupport) {
15162           SendToProgram("exit\nforce\n", &first);
15163           first.analyzing = FALSE;
15164         }
15165     }
15166
15167     if (gameMode == IcsExamining && !pausing) {
15168         SendToICS(ics_prefix);
15169         SendToICS("backward 999999\n");
15170     } else {
15171         BackwardInner(backwardMostMove);
15172     }
15173
15174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15175         /* we have fed all the moves, so reactivate analysis mode */
15176         SendToProgram("analyze\n", &first);
15177         first.analyzing = TRUE;
15178         /*first.maybeThinking = TRUE;*/
15179         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15180     }
15181 }
15182
15183 void
15184 ToNrEvent (int to)
15185 {
15186   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15187   if (to >= forwardMostMove) to = forwardMostMove;
15188   if (to <= backwardMostMove) to = backwardMostMove;
15189   if (to < currentMove) {
15190     BackwardInner(to);
15191   } else {
15192     ForwardInner(to);
15193   }
15194 }
15195
15196 void
15197 RevertEvent (Boolean annotate)
15198 {
15199     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15200         return;
15201     }
15202     if (gameMode != IcsExamining) {
15203         DisplayError(_("You are not examining a game"), 0);
15204         return;
15205     }
15206     if (pausing) {
15207         DisplayError(_("You can't revert while pausing"), 0);
15208         return;
15209     }
15210     SendToICS(ics_prefix);
15211     SendToICS("revert\n");
15212 }
15213
15214 void
15215 RetractMoveEvent ()
15216 {
15217     switch (gameMode) {
15218       case MachinePlaysWhite:
15219       case MachinePlaysBlack:
15220         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15221             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15222             return;
15223         }
15224         if (forwardMostMove < 2) return;
15225         currentMove = forwardMostMove = forwardMostMove - 2;
15226         whiteTimeRemaining = timeRemaining[0][currentMove];
15227         blackTimeRemaining = timeRemaining[1][currentMove];
15228         DisplayBothClocks();
15229         DisplayMove(currentMove - 1);
15230         ClearHighlights();/*!! could figure this out*/
15231         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15232         SendToProgram("remove\n", &first);
15233         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15234         break;
15235
15236       case BeginningOfGame:
15237       default:
15238         break;
15239
15240       case IcsPlayingWhite:
15241       case IcsPlayingBlack:
15242         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15243             SendToICS(ics_prefix);
15244             SendToICS("takeback 2\n");
15245         } else {
15246             SendToICS(ics_prefix);
15247             SendToICS("takeback 1\n");
15248         }
15249         break;
15250     }
15251 }
15252
15253 void
15254 MoveNowEvent ()
15255 {
15256     ChessProgramState *cps;
15257
15258     switch (gameMode) {
15259       case MachinePlaysWhite:
15260         if (!WhiteOnMove(forwardMostMove)) {
15261             DisplayError(_("It is your turn"), 0);
15262             return;
15263         }
15264         cps = &first;
15265         break;
15266       case MachinePlaysBlack:
15267         if (WhiteOnMove(forwardMostMove)) {
15268             DisplayError(_("It is your turn"), 0);
15269             return;
15270         }
15271         cps = &first;
15272         break;
15273       case TwoMachinesPlay:
15274         if (WhiteOnMove(forwardMostMove) ==
15275             (first.twoMachinesColor[0] == 'w')) {
15276             cps = &first;
15277         } else {
15278             cps = &second;
15279         }
15280         break;
15281       case BeginningOfGame:
15282       default:
15283         return;
15284     }
15285     SendToProgram("?\n", cps);
15286 }
15287
15288 void
15289 TruncateGameEvent ()
15290 {
15291     EditGameEvent();
15292     if (gameMode != EditGame) return;
15293     TruncateGame();
15294 }
15295
15296 void
15297 TruncateGame ()
15298 {
15299     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15300     if (forwardMostMove > currentMove) {
15301         if (gameInfo.resultDetails != NULL) {
15302             free(gameInfo.resultDetails);
15303             gameInfo.resultDetails = NULL;
15304             gameInfo.result = GameUnfinished;
15305         }
15306         forwardMostMove = currentMove;
15307         HistorySet(parseList, backwardMostMove, forwardMostMove,
15308                    currentMove-1);
15309     }
15310 }
15311
15312 void
15313 HintEvent ()
15314 {
15315     if (appData.noChessProgram) return;
15316     switch (gameMode) {
15317       case MachinePlaysWhite:
15318         if (WhiteOnMove(forwardMostMove)) {
15319             DisplayError(_("Wait until your turn"), 0);
15320             return;
15321         }
15322         break;
15323       case BeginningOfGame:
15324       case MachinePlaysBlack:
15325         if (!WhiteOnMove(forwardMostMove)) {
15326             DisplayError(_("Wait until your turn"), 0);
15327             return;
15328         }
15329         break;
15330       default:
15331         DisplayError(_("No hint available"), 0);
15332         return;
15333     }
15334     SendToProgram("hint\n", &first);
15335     hintRequested = TRUE;
15336 }
15337
15338 void
15339 CreateBookEvent ()
15340 {
15341     ListGame * lg = (ListGame *) gameList.head;
15342     FILE *f, *g;
15343     int nItem;
15344     static int secondTime = FALSE;
15345
15346     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15347         DisplayError(_("Game list not loaded or empty"), 0);
15348         return;
15349     }
15350
15351     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15352         fclose(g);
15353         secondTime++;
15354         DisplayNote(_("Book file exists! Try again for overwrite."));
15355         return;
15356     }
15357
15358     creatingBook = TRUE;
15359     secondTime = FALSE;
15360
15361     /* Get list size */
15362     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15363         LoadGame(f, nItem, "", TRUE);
15364         AddGameToBook(TRUE);
15365         lg = (ListGame *) lg->node.succ;
15366     }
15367
15368     creatingBook = FALSE;
15369     FlushBook();
15370 }
15371
15372 void
15373 BookEvent ()
15374 {
15375     if (appData.noChessProgram) return;
15376     switch (gameMode) {
15377       case MachinePlaysWhite:
15378         if (WhiteOnMove(forwardMostMove)) {
15379             DisplayError(_("Wait until your turn"), 0);
15380             return;
15381         }
15382         break;
15383       case BeginningOfGame:
15384       case MachinePlaysBlack:
15385         if (!WhiteOnMove(forwardMostMove)) {
15386             DisplayError(_("Wait until your turn"), 0);
15387             return;
15388         }
15389         break;
15390       case EditPosition:
15391         EditPositionDone(TRUE);
15392         break;
15393       case TwoMachinesPlay:
15394         return;
15395       default:
15396         break;
15397     }
15398     SendToProgram("bk\n", &first);
15399     bookOutput[0] = NULLCHAR;
15400     bookRequested = TRUE;
15401 }
15402
15403 void
15404 AboutGameEvent ()
15405 {
15406     char *tags = PGNTags(&gameInfo);
15407     TagsPopUp(tags, CmailMsg());
15408     free(tags);
15409 }
15410
15411 /* end button procedures */
15412
15413 void
15414 PrintPosition (FILE *fp, int move)
15415 {
15416     int i, j;
15417
15418     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15419         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15420             char c = PieceToChar(boards[move][i][j]);
15421             fputc(c == 'x' ? '.' : c, fp);
15422             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15423         }
15424     }
15425     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15426       fprintf(fp, "white to play\n");
15427     else
15428       fprintf(fp, "black to play\n");
15429 }
15430
15431 void
15432 PrintOpponents (FILE *fp)
15433 {
15434     if (gameInfo.white != NULL) {
15435         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15436     } else {
15437         fprintf(fp, "\n");
15438     }
15439 }
15440
15441 /* Find last component of program's own name, using some heuristics */
15442 void
15443 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15444 {
15445     char *p, *q, c;
15446     int local = (strcmp(host, "localhost") == 0);
15447     while (!local && (p = strchr(prog, ';')) != NULL) {
15448         p++;
15449         while (*p == ' ') p++;
15450         prog = p;
15451     }
15452     if (*prog == '"' || *prog == '\'') {
15453         q = strchr(prog + 1, *prog);
15454     } else {
15455         q = strchr(prog, ' ');
15456     }
15457     if (q == NULL) q = prog + strlen(prog);
15458     p = q;
15459     while (p >= prog && *p != '/' && *p != '\\') p--;
15460     p++;
15461     if(p == prog && *p == '"') p++;
15462     c = *q; *q = 0;
15463     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15464     memcpy(buf, p, q - p);
15465     buf[q - p] = NULLCHAR;
15466     if (!local) {
15467         strcat(buf, "@");
15468         strcat(buf, host);
15469     }
15470 }
15471
15472 char *
15473 TimeControlTagValue ()
15474 {
15475     char buf[MSG_SIZ];
15476     if (!appData.clockMode) {
15477       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15478     } else if (movesPerSession > 0) {
15479       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15480     } else if (timeIncrement == 0) {
15481       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15482     } else {
15483       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15484     }
15485     return StrSave(buf);
15486 }
15487
15488 void
15489 SetGameInfo ()
15490 {
15491     /* This routine is used only for certain modes */
15492     VariantClass v = gameInfo.variant;
15493     ChessMove r = GameUnfinished;
15494     char *p = NULL;
15495
15496     if(keepInfo) return;
15497
15498     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15499         r = gameInfo.result;
15500         p = gameInfo.resultDetails;
15501         gameInfo.resultDetails = NULL;
15502     }
15503     ClearGameInfo(&gameInfo);
15504     gameInfo.variant = v;
15505
15506     switch (gameMode) {
15507       case MachinePlaysWhite:
15508         gameInfo.event = StrSave( appData.pgnEventHeader );
15509         gameInfo.site = StrSave(HostName());
15510         gameInfo.date = PGNDate();
15511         gameInfo.round = StrSave("-");
15512         gameInfo.white = StrSave(first.tidy);
15513         gameInfo.black = StrSave(UserName());
15514         gameInfo.timeControl = TimeControlTagValue();
15515         break;
15516
15517       case MachinePlaysBlack:
15518         gameInfo.event = StrSave( appData.pgnEventHeader );
15519         gameInfo.site = StrSave(HostName());
15520         gameInfo.date = PGNDate();
15521         gameInfo.round = StrSave("-");
15522         gameInfo.white = StrSave(UserName());
15523         gameInfo.black = StrSave(first.tidy);
15524         gameInfo.timeControl = TimeControlTagValue();
15525         break;
15526
15527       case TwoMachinesPlay:
15528         gameInfo.event = StrSave( appData.pgnEventHeader );
15529         gameInfo.site = StrSave(HostName());
15530         gameInfo.date = PGNDate();
15531         if (roundNr > 0) {
15532             char buf[MSG_SIZ];
15533             snprintf(buf, MSG_SIZ, "%d", roundNr);
15534             gameInfo.round = StrSave(buf);
15535         } else {
15536             gameInfo.round = StrSave("-");
15537         }
15538         if (first.twoMachinesColor[0] == 'w') {
15539             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15540             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15541         } else {
15542             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15543             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15544         }
15545         gameInfo.timeControl = TimeControlTagValue();
15546         break;
15547
15548       case EditGame:
15549         gameInfo.event = StrSave("Edited game");
15550         gameInfo.site = StrSave(HostName());
15551         gameInfo.date = PGNDate();
15552         gameInfo.round = StrSave("-");
15553         gameInfo.white = StrSave("-");
15554         gameInfo.black = StrSave("-");
15555         gameInfo.result = r;
15556         gameInfo.resultDetails = p;
15557         break;
15558
15559       case EditPosition:
15560         gameInfo.event = StrSave("Edited position");
15561         gameInfo.site = StrSave(HostName());
15562         gameInfo.date = PGNDate();
15563         gameInfo.round = StrSave("-");
15564         gameInfo.white = StrSave("-");
15565         gameInfo.black = StrSave("-");
15566         break;
15567
15568       case IcsPlayingWhite:
15569       case IcsPlayingBlack:
15570       case IcsObserving:
15571       case IcsExamining:
15572         break;
15573
15574       case PlayFromGameFile:
15575         gameInfo.event = StrSave("Game from non-PGN file");
15576         gameInfo.site = StrSave(HostName());
15577         gameInfo.date = PGNDate();
15578         gameInfo.round = StrSave("-");
15579         gameInfo.white = StrSave("?");
15580         gameInfo.black = StrSave("?");
15581         break;
15582
15583       default:
15584         break;
15585     }
15586 }
15587
15588 void
15589 ReplaceComment (int index, char *text)
15590 {
15591     int len;
15592     char *p;
15593     float score;
15594
15595     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15596        pvInfoList[index-1].depth == len &&
15597        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15598        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15599     while (*text == '\n') text++;
15600     len = strlen(text);
15601     while (len > 0 && text[len - 1] == '\n') len--;
15602
15603     if (commentList[index] != NULL)
15604       free(commentList[index]);
15605
15606     if (len == 0) {
15607         commentList[index] = NULL;
15608         return;
15609     }
15610   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15611       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15612       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15613     commentList[index] = (char *) malloc(len + 2);
15614     strncpy(commentList[index], text, len);
15615     commentList[index][len] = '\n';
15616     commentList[index][len + 1] = NULLCHAR;
15617   } else {
15618     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15619     char *p;
15620     commentList[index] = (char *) malloc(len + 7);
15621     safeStrCpy(commentList[index], "{\n", 3);
15622     safeStrCpy(commentList[index]+2, text, len+1);
15623     commentList[index][len+2] = NULLCHAR;
15624     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15625     strcat(commentList[index], "\n}\n");
15626   }
15627 }
15628
15629 void
15630 CrushCRs (char *text)
15631 {
15632   char *p = text;
15633   char *q = text;
15634   char ch;
15635
15636   do {
15637     ch = *p++;
15638     if (ch == '\r') continue;
15639     *q++ = ch;
15640   } while (ch != '\0');
15641 }
15642
15643 void
15644 AppendComment (int index, char *text, Boolean addBraces)
15645 /* addBraces  tells if we should add {} */
15646 {
15647     int oldlen, len;
15648     char *old;
15649
15650 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15651     if(addBraces == 3) addBraces = 0; else // force appending literally
15652     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15653
15654     CrushCRs(text);
15655     while (*text == '\n') text++;
15656     len = strlen(text);
15657     while (len > 0 && text[len - 1] == '\n') len--;
15658     text[len] = NULLCHAR;
15659
15660     if (len == 0) return;
15661
15662     if (commentList[index] != NULL) {
15663       Boolean addClosingBrace = addBraces;
15664         old = commentList[index];
15665         oldlen = strlen(old);
15666         while(commentList[index][oldlen-1] ==  '\n')
15667           commentList[index][--oldlen] = NULLCHAR;
15668         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15669         safeStrCpy(commentList[index], old, oldlen + len + 6);
15670         free(old);
15671         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15672         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15673           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15674           while (*text == '\n') { text++; len--; }
15675           commentList[index][--oldlen] = NULLCHAR;
15676       }
15677         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15678         else          strcat(commentList[index], "\n");
15679         strcat(commentList[index], text);
15680         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15681         else          strcat(commentList[index], "\n");
15682     } else {
15683         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15684         if(addBraces)
15685           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15686         else commentList[index][0] = NULLCHAR;
15687         strcat(commentList[index], text);
15688         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15689         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15690     }
15691 }
15692
15693 static char *
15694 FindStr (char * text, char * sub_text)
15695 {
15696     char * result = strstr( text, sub_text );
15697
15698     if( result != NULL ) {
15699         result += strlen( sub_text );
15700     }
15701
15702     return result;
15703 }
15704
15705 /* [AS] Try to extract PV info from PGN comment */
15706 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15707 char *
15708 GetInfoFromComment (int index, char * text)
15709 {
15710     char * sep = text, *p;
15711
15712     if( text != NULL && index > 0 ) {
15713         int score = 0;
15714         int depth = 0;
15715         int time = -1, sec = 0, deci;
15716         char * s_eval = FindStr( text, "[%eval " );
15717         char * s_emt = FindStr( text, "[%emt " );
15718 #if 0
15719         if( s_eval != NULL || s_emt != NULL ) {
15720 #else
15721         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15722 #endif
15723             /* New style */
15724             char delim;
15725
15726             if( s_eval != NULL ) {
15727                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15728                     return text;
15729                 }
15730
15731                 if( delim != ']' ) {
15732                     return text;
15733                 }
15734             }
15735
15736             if( s_emt != NULL ) {
15737             }
15738                 return text;
15739         }
15740         else {
15741             /* We expect something like: [+|-]nnn.nn/dd */
15742             int score_lo = 0;
15743
15744             if(*text != '{') return text; // [HGM] braces: must be normal comment
15745
15746             sep = strchr( text, '/' );
15747             if( sep == NULL || sep < (text+4) ) {
15748                 return text;
15749             }
15750
15751             p = text;
15752             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15753             if(p[1] == '(') { // comment starts with PV
15754                p = strchr(p, ')'); // locate end of PV
15755                if(p == NULL || sep < p+5) return text;
15756                // at this point we have something like "{(.*) +0.23/6 ..."
15757                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15758                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15759                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15760             }
15761             time = -1; sec = -1; deci = -1;
15762             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15763                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15764                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15765                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15766                 return text;
15767             }
15768
15769             if( score_lo < 0 || score_lo >= 100 ) {
15770                 return text;
15771             }
15772
15773             if(sec >= 0) time = 600*time + 10*sec; else
15774             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15775
15776             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15777
15778             /* [HGM] PV time: now locate end of PV info */
15779             while( *++sep >= '0' && *sep <= '9'); // strip depth
15780             if(time >= 0)
15781             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15782             if(sec >= 0)
15783             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15784             if(deci >= 0)
15785             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15786             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15787         }
15788
15789         if( depth <= 0 ) {
15790             return text;
15791         }
15792
15793         if( time < 0 ) {
15794             time = -1;
15795         }
15796
15797         pvInfoList[index-1].depth = depth;
15798         pvInfoList[index-1].score = score;
15799         pvInfoList[index-1].time  = 10*time; // centi-sec
15800         if(*sep == '}') *sep = 0; else *--sep = '{';
15801         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15802     }
15803     return sep;
15804 }
15805
15806 void
15807 SendToProgram (char *message, ChessProgramState *cps)
15808 {
15809     int count, outCount, error;
15810     char buf[MSG_SIZ];
15811
15812     if (cps->pr == NoProc) return;
15813     Attention(cps);
15814
15815     if (appData.debugMode) {
15816         TimeMark now;
15817         GetTimeMark(&now);
15818         fprintf(debugFP, "%ld >%-6s: %s",
15819                 SubtractTimeMarks(&now, &programStartTime),
15820                 cps->which, message);
15821         if(serverFP)
15822             fprintf(serverFP, "%ld >%-6s: %s",
15823                 SubtractTimeMarks(&now, &programStartTime),
15824                 cps->which, message), fflush(serverFP);
15825     }
15826
15827     count = strlen(message);
15828     outCount = OutputToProcess(cps->pr, message, count, &error);
15829     if (outCount < count && !exiting
15830                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15831       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15832       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15833         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15834             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15835                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15836                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15837                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15838             } else {
15839                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15840                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15841                 gameInfo.result = res;
15842             }
15843             gameInfo.resultDetails = StrSave(buf);
15844         }
15845         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15846         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15847     }
15848 }
15849
15850 void
15851 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15852 {
15853     char *end_str;
15854     char buf[MSG_SIZ];
15855     ChessProgramState *cps = (ChessProgramState *)closure;
15856
15857     if (isr != cps->isr) return; /* Killed intentionally */
15858     if (count <= 0) {
15859         if (count == 0) {
15860             RemoveInputSource(cps->isr);
15861             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15862                     _(cps->which), cps->program);
15863             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15864             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15865                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15866                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15867                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15868                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15869                 } else {
15870                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15871                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15872                     gameInfo.result = res;
15873                 }
15874                 gameInfo.resultDetails = StrSave(buf);
15875             }
15876             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15877             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15878         } else {
15879             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15880                     _(cps->which), cps->program);
15881             RemoveInputSource(cps->isr);
15882
15883             /* [AS] Program is misbehaving badly... kill it */
15884             if( count == -2 ) {
15885                 DestroyChildProcess( cps->pr, 9 );
15886                 cps->pr = NoProc;
15887             }
15888
15889             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15890         }
15891         return;
15892     }
15893
15894     if ((end_str = strchr(message, '\r')) != NULL)
15895       *end_str = NULLCHAR;
15896     if ((end_str = strchr(message, '\n')) != NULL)
15897       *end_str = NULLCHAR;
15898
15899     if (appData.debugMode) {
15900         TimeMark now; int print = 1;
15901         char *quote = ""; char c; int i;
15902
15903         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15904                 char start = message[0];
15905                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15906                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15907                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15908                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15909                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15910                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15911                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15912                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15913                    sscanf(message, "hint: %c", &c)!=1 &&
15914                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15915                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15916                     print = (appData.engineComments >= 2);
15917                 }
15918                 message[0] = start; // restore original message
15919         }
15920         if(print) {
15921                 GetTimeMark(&now);
15922                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15923                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15924                         quote,
15925                         message);
15926                 if(serverFP)
15927                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15928                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15929                         quote,
15930                         message), fflush(serverFP);
15931         }
15932     }
15933
15934     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15935     if (appData.icsEngineAnalyze) {
15936         if (strstr(message, "whisper") != NULL ||
15937              strstr(message, "kibitz") != NULL ||
15938             strstr(message, "tellics") != NULL) return;
15939     }
15940
15941     HandleMachineMove(message, cps);
15942 }
15943
15944
15945 void
15946 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15947 {
15948     char buf[MSG_SIZ];
15949     int seconds;
15950
15951     if( timeControl_2 > 0 ) {
15952         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15953             tc = timeControl_2;
15954         }
15955     }
15956     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15957     inc /= cps->timeOdds;
15958     st  /= cps->timeOdds;
15959
15960     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15961
15962     if (st > 0) {
15963       /* Set exact time per move, normally using st command */
15964       if (cps->stKludge) {
15965         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15966         seconds = st % 60;
15967         if (seconds == 0) {
15968           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15969         } else {
15970           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15971         }
15972       } else {
15973         snprintf(buf, MSG_SIZ, "st %d\n", st);
15974       }
15975     } else {
15976       /* Set conventional or incremental time control, using level command */
15977       if (seconds == 0) {
15978         /* Note old gnuchess bug -- minutes:seconds used to not work.
15979            Fixed in later versions, but still avoid :seconds
15980            when seconds is 0. */
15981         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15982       } else {
15983         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15984                  seconds, inc/1000.);
15985       }
15986     }
15987     SendToProgram(buf, cps);
15988
15989     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15990     /* Orthogonally, limit search to given depth */
15991     if (sd > 0) {
15992       if (cps->sdKludge) {
15993         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15994       } else {
15995         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15996       }
15997       SendToProgram(buf, cps);
15998     }
15999
16000     if(cps->nps >= 0) { /* [HGM] nps */
16001         if(cps->supportsNPS == FALSE)
16002           cps->nps = -1; // don't use if engine explicitly says not supported!
16003         else {
16004           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16005           SendToProgram(buf, cps);
16006         }
16007     }
16008 }
16009
16010 ChessProgramState *
16011 WhitePlayer ()
16012 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16013 {
16014     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16015        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16016         return &second;
16017     return &first;
16018 }
16019
16020 void
16021 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16022 {
16023     char message[MSG_SIZ];
16024     long time, otime;
16025
16026     /* Note: this routine must be called when the clocks are stopped
16027        or when they have *just* been set or switched; otherwise
16028        it will be off by the time since the current tick started.
16029     */
16030     if (machineWhite) {
16031         time = whiteTimeRemaining / 10;
16032         otime = blackTimeRemaining / 10;
16033     } else {
16034         time = blackTimeRemaining / 10;
16035         otime = whiteTimeRemaining / 10;
16036     }
16037     /* [HGM] translate opponent's time by time-odds factor */
16038     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16039
16040     if (time <= 0) time = 1;
16041     if (otime <= 0) otime = 1;
16042
16043     snprintf(message, MSG_SIZ, "time %ld\n", time);
16044     SendToProgram(message, cps);
16045
16046     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16047     SendToProgram(message, cps);
16048 }
16049
16050 int
16051 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16052 {
16053   char buf[MSG_SIZ];
16054   int len = strlen(name);
16055   int val;
16056
16057   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16058     (*p) += len + 1;
16059     sscanf(*p, "%d", &val);
16060     *loc = (val != 0);
16061     while (**p && **p != ' ')
16062       (*p)++;
16063     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16064     SendToProgram(buf, cps);
16065     return TRUE;
16066   }
16067   return FALSE;
16068 }
16069
16070 int
16071 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16072 {
16073   char buf[MSG_SIZ];
16074   int len = strlen(name);
16075   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16076     (*p) += len + 1;
16077     sscanf(*p, "%d", loc);
16078     while (**p && **p != ' ') (*p)++;
16079     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16080     SendToProgram(buf, cps);
16081     return TRUE;
16082   }
16083   return FALSE;
16084 }
16085
16086 int
16087 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16088 {
16089   char buf[MSG_SIZ];
16090   int len = strlen(name);
16091   if (strncmp((*p), name, len) == 0
16092       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16093     (*p) += len + 2;
16094     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16095     sscanf(*p, "%[^\"]", *loc);
16096     while (**p && **p != '\"') (*p)++;
16097     if (**p == '\"') (*p)++;
16098     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16099     SendToProgram(buf, cps);
16100     return TRUE;
16101   }
16102   return FALSE;
16103 }
16104
16105 int
16106 ParseOption (Option *opt, ChessProgramState *cps)
16107 // [HGM] options: process the string that defines an engine option, and determine
16108 // name, type, default value, and allowed value range
16109 {
16110         char *p, *q, buf[MSG_SIZ];
16111         int n, min = (-1)<<31, max = 1<<31, def;
16112
16113         if(p = strstr(opt->name, " -spin ")) {
16114             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16115             if(max < min) max = min; // enforce consistency
16116             if(def < min) def = min;
16117             if(def > max) def = max;
16118             opt->value = def;
16119             opt->min = min;
16120             opt->max = max;
16121             opt->type = Spin;
16122         } else if((p = strstr(opt->name, " -slider "))) {
16123             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16124             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16125             if(max < min) max = min; // enforce consistency
16126             if(def < min) def = min;
16127             if(def > max) def = max;
16128             opt->value = def;
16129             opt->min = min;
16130             opt->max = max;
16131             opt->type = Spin; // Slider;
16132         } else if((p = strstr(opt->name, " -string "))) {
16133             opt->textValue = p+9;
16134             opt->type = TextBox;
16135         } else if((p = strstr(opt->name, " -file "))) {
16136             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16137             opt->textValue = p+7;
16138             opt->type = FileName; // FileName;
16139         } else if((p = strstr(opt->name, " -path "))) {
16140             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16141             opt->textValue = p+7;
16142             opt->type = PathName; // PathName;
16143         } else if(p = strstr(opt->name, " -check ")) {
16144             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16145             opt->value = (def != 0);
16146             opt->type = CheckBox;
16147         } else if(p = strstr(opt->name, " -combo ")) {
16148             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16149             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16150             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16151             opt->value = n = 0;
16152             while(q = StrStr(q, " /// ")) {
16153                 n++; *q = 0;    // count choices, and null-terminate each of them
16154                 q += 5;
16155                 if(*q == '*') { // remember default, which is marked with * prefix
16156                     q++;
16157                     opt->value = n;
16158                 }
16159                 cps->comboList[cps->comboCnt++] = q;
16160             }
16161             cps->comboList[cps->comboCnt++] = NULL;
16162             opt->max = n + 1;
16163             opt->type = ComboBox;
16164         } else if(p = strstr(opt->name, " -button")) {
16165             opt->type = Button;
16166         } else if(p = strstr(opt->name, " -save")) {
16167             opt->type = SaveButton;
16168         } else return FALSE;
16169         *p = 0; // terminate option name
16170         // now look if the command-line options define a setting for this engine option.
16171         if(cps->optionSettings && cps->optionSettings[0])
16172             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16173         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16174           snprintf(buf, MSG_SIZ, "option %s", p);
16175                 if(p = strstr(buf, ",")) *p = 0;
16176                 if(q = strchr(buf, '=')) switch(opt->type) {
16177                     case ComboBox:
16178                         for(n=0; n<opt->max; n++)
16179                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16180                         break;
16181                     case TextBox:
16182                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16183                         break;
16184                     case Spin:
16185                     case CheckBox:
16186                         opt->value = atoi(q+1);
16187                     default:
16188                         break;
16189                 }
16190                 strcat(buf, "\n");
16191                 SendToProgram(buf, cps);
16192         }
16193         return TRUE;
16194 }
16195
16196 void
16197 FeatureDone (ChessProgramState *cps, int val)
16198 {
16199   DelayedEventCallback cb = GetDelayedEvent();
16200   if ((cb == InitBackEnd3 && cps == &first) ||
16201       (cb == SettingsMenuIfReady && cps == &second) ||
16202       (cb == LoadEngine) ||
16203       (cb == TwoMachinesEventIfReady)) {
16204     CancelDelayedEvent();
16205     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16206   }
16207   cps->initDone = val;
16208   if(val) cps->reload = FALSE;
16209 }
16210
16211 /* Parse feature command from engine */
16212 void
16213 ParseFeatures (char *args, ChessProgramState *cps)
16214 {
16215   char *p = args;
16216   char *q = NULL;
16217   int val;
16218   char buf[MSG_SIZ];
16219
16220   for (;;) {
16221     while (*p == ' ') p++;
16222     if (*p == NULLCHAR) return;
16223
16224     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16225     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16226     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16227     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16228     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16229     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16230     if (BoolFeature(&p, "reuse", &val, cps)) {
16231       /* Engine can disable reuse, but can't enable it if user said no */
16232       if (!val) cps->reuse = FALSE;
16233       continue;
16234     }
16235     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16236     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16237       if (gameMode == TwoMachinesPlay) {
16238         DisplayTwoMachinesTitle();
16239       } else {
16240         DisplayTitle("");
16241       }
16242       continue;
16243     }
16244     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16245     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16246     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16247     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16248     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16249     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16250     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16251     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16252     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16253     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16254     if (IntFeature(&p, "done", &val, cps)) {
16255       FeatureDone(cps, val);
16256       continue;
16257     }
16258     /* Added by Tord: */
16259     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16260     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16261     /* End of additions by Tord */
16262
16263     /* [HGM] added features: */
16264     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16265     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16266     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16267     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16268     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16269     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16270     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16271     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16272         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16273         FREE(cps->option[cps->nrOptions].name);
16274         cps->option[cps->nrOptions].name = q; q = NULL;
16275         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16276           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16277             SendToProgram(buf, cps);
16278             continue;
16279         }
16280         if(cps->nrOptions >= MAX_OPTIONS) {
16281             cps->nrOptions--;
16282             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16283             DisplayError(buf, 0);
16284         }
16285         continue;
16286     }
16287     /* End of additions by HGM */
16288
16289     /* unknown feature: complain and skip */
16290     q = p;
16291     while (*q && *q != '=') q++;
16292     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16293     SendToProgram(buf, cps);
16294     p = q;
16295     if (*p == '=') {
16296       p++;
16297       if (*p == '\"') {
16298         p++;
16299         while (*p && *p != '\"') p++;
16300         if (*p == '\"') p++;
16301       } else {
16302         while (*p && *p != ' ') p++;
16303       }
16304     }
16305   }
16306
16307 }
16308
16309 void
16310 PeriodicUpdatesEvent (int newState)
16311 {
16312     if (newState == appData.periodicUpdates)
16313       return;
16314
16315     appData.periodicUpdates=newState;
16316
16317     /* Display type changes, so update it now */
16318 //    DisplayAnalysis();
16319
16320     /* Get the ball rolling again... */
16321     if (newState) {
16322         AnalysisPeriodicEvent(1);
16323         StartAnalysisClock();
16324     }
16325 }
16326
16327 void
16328 PonderNextMoveEvent (int newState)
16329 {
16330     if (newState == appData.ponderNextMove) return;
16331     if (gameMode == EditPosition) EditPositionDone(TRUE);
16332     if (newState) {
16333         SendToProgram("hard\n", &first);
16334         if (gameMode == TwoMachinesPlay) {
16335             SendToProgram("hard\n", &second);
16336         }
16337     } else {
16338         SendToProgram("easy\n", &first);
16339         thinkOutput[0] = NULLCHAR;
16340         if (gameMode == TwoMachinesPlay) {
16341             SendToProgram("easy\n", &second);
16342         }
16343     }
16344     appData.ponderNextMove = newState;
16345 }
16346
16347 void
16348 NewSettingEvent (int option, int *feature, char *command, int value)
16349 {
16350     char buf[MSG_SIZ];
16351
16352     if (gameMode == EditPosition) EditPositionDone(TRUE);
16353     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16354     if(feature == NULL || *feature) SendToProgram(buf, &first);
16355     if (gameMode == TwoMachinesPlay) {
16356         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16357     }
16358 }
16359
16360 void
16361 ShowThinkingEvent ()
16362 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16363 {
16364     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16365     int newState = appData.showThinking
16366         // [HGM] thinking: other features now need thinking output as well
16367         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16368
16369     if (oldState == newState) return;
16370     oldState = newState;
16371     if (gameMode == EditPosition) EditPositionDone(TRUE);
16372     if (oldState) {
16373         SendToProgram("post\n", &first);
16374         if (gameMode == TwoMachinesPlay) {
16375             SendToProgram("post\n", &second);
16376         }
16377     } else {
16378         SendToProgram("nopost\n", &first);
16379         thinkOutput[0] = NULLCHAR;
16380         if (gameMode == TwoMachinesPlay) {
16381             SendToProgram("nopost\n", &second);
16382         }
16383     }
16384 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16385 }
16386
16387 void
16388 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16389 {
16390   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16391   if (pr == NoProc) return;
16392   AskQuestion(title, question, replyPrefix, pr);
16393 }
16394
16395 void
16396 TypeInEvent (char firstChar)
16397 {
16398     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16399         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16400         gameMode == AnalyzeMode || gameMode == EditGame ||
16401         gameMode == EditPosition || gameMode == IcsExamining ||
16402         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16403         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16404                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16405                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16406         gameMode == Training) PopUpMoveDialog(firstChar);
16407 }
16408
16409 void
16410 TypeInDoneEvent (char *move)
16411 {
16412         Board board;
16413         int n, fromX, fromY, toX, toY;
16414         char promoChar;
16415         ChessMove moveType;
16416
16417         // [HGM] FENedit
16418         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16419                 EditPositionPasteFEN(move);
16420                 return;
16421         }
16422         // [HGM] movenum: allow move number to be typed in any mode
16423         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16424           ToNrEvent(2*n-1);
16425           return;
16426         }
16427         // undocumented kludge: allow command-line option to be typed in!
16428         // (potentially fatal, and does not implement the effect of the option.)
16429         // should only be used for options that are values on which future decisions will be made,
16430         // and definitely not on options that would be used during initialization.
16431         if(strstr(move, "!!! -") == move) {
16432             ParseArgsFromString(move+4);
16433             return;
16434         }
16435
16436       if (gameMode != EditGame && currentMove != forwardMostMove &&
16437         gameMode != Training) {
16438         DisplayMoveError(_("Displayed move is not current"));
16439       } else {
16440         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16441           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16442         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16443         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16444           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16445           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16446         } else {
16447           DisplayMoveError(_("Could not parse move"));
16448         }
16449       }
16450 }
16451
16452 void
16453 DisplayMove (int moveNumber)
16454 {
16455     char message[MSG_SIZ];
16456     char res[MSG_SIZ];
16457     char cpThinkOutput[MSG_SIZ];
16458
16459     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16460
16461     if (moveNumber == forwardMostMove - 1 ||
16462         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16463
16464         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16465
16466         if (strchr(cpThinkOutput, '\n')) {
16467             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16468         }
16469     } else {
16470         *cpThinkOutput = NULLCHAR;
16471     }
16472
16473     /* [AS] Hide thinking from human user */
16474     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16475         *cpThinkOutput = NULLCHAR;
16476         if( thinkOutput[0] != NULLCHAR ) {
16477             int i;
16478
16479             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16480                 cpThinkOutput[i] = '.';
16481             }
16482             cpThinkOutput[i] = NULLCHAR;
16483             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16484         }
16485     }
16486
16487     if (moveNumber == forwardMostMove - 1 &&
16488         gameInfo.resultDetails != NULL) {
16489         if (gameInfo.resultDetails[0] == NULLCHAR) {
16490           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16491         } else {
16492           snprintf(res, MSG_SIZ, " {%s} %s",
16493                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16494         }
16495     } else {
16496         res[0] = NULLCHAR;
16497     }
16498
16499     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16500         DisplayMessage(res, cpThinkOutput);
16501     } else {
16502       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16503                 WhiteOnMove(moveNumber) ? " " : ".. ",
16504                 parseList[moveNumber], res);
16505         DisplayMessage(message, cpThinkOutput);
16506     }
16507 }
16508
16509 void
16510 DisplayComment (int moveNumber, char *text)
16511 {
16512     char title[MSG_SIZ];
16513
16514     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16515       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16516     } else {
16517       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16518               WhiteOnMove(moveNumber) ? " " : ".. ",
16519               parseList[moveNumber]);
16520     }
16521     if (text != NULL && (appData.autoDisplayComment || commentUp))
16522         CommentPopUp(title, text);
16523 }
16524
16525 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16526  * might be busy thinking or pondering.  It can be omitted if your
16527  * gnuchess is configured to stop thinking immediately on any user
16528  * input.  However, that gnuchess feature depends on the FIONREAD
16529  * ioctl, which does not work properly on some flavors of Unix.
16530  */
16531 void
16532 Attention (ChessProgramState *cps)
16533 {
16534 #if ATTENTION
16535     if (!cps->useSigint) return;
16536     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16537     switch (gameMode) {
16538       case MachinePlaysWhite:
16539       case MachinePlaysBlack:
16540       case TwoMachinesPlay:
16541       case IcsPlayingWhite:
16542       case IcsPlayingBlack:
16543       case AnalyzeMode:
16544       case AnalyzeFile:
16545         /* Skip if we know it isn't thinking */
16546         if (!cps->maybeThinking) return;
16547         if (appData.debugMode)
16548           fprintf(debugFP, "Interrupting %s\n", cps->which);
16549         InterruptChildProcess(cps->pr);
16550         cps->maybeThinking = FALSE;
16551         break;
16552       default:
16553         break;
16554     }
16555 #endif /*ATTENTION*/
16556 }
16557
16558 int
16559 CheckFlags ()
16560 {
16561     if (whiteTimeRemaining <= 0) {
16562         if (!whiteFlag) {
16563             whiteFlag = TRUE;
16564             if (appData.icsActive) {
16565                 if (appData.autoCallFlag &&
16566                     gameMode == IcsPlayingBlack && !blackFlag) {
16567                   SendToICS(ics_prefix);
16568                   SendToICS("flag\n");
16569                 }
16570             } else {
16571                 if (blackFlag) {
16572                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16573                 } else {
16574                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16575                     if (appData.autoCallFlag) {
16576                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16577                         return TRUE;
16578                     }
16579                 }
16580             }
16581         }
16582     }
16583     if (blackTimeRemaining <= 0) {
16584         if (!blackFlag) {
16585             blackFlag = TRUE;
16586             if (appData.icsActive) {
16587                 if (appData.autoCallFlag &&
16588                     gameMode == IcsPlayingWhite && !whiteFlag) {
16589                   SendToICS(ics_prefix);
16590                   SendToICS("flag\n");
16591                 }
16592             } else {
16593                 if (whiteFlag) {
16594                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16595                 } else {
16596                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16597                     if (appData.autoCallFlag) {
16598                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16599                         return TRUE;
16600                     }
16601                 }
16602             }
16603         }
16604     }
16605     return FALSE;
16606 }
16607
16608 void
16609 CheckTimeControl ()
16610 {
16611     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16612         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16613
16614     /*
16615      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16616      */
16617     if ( !WhiteOnMove(forwardMostMove) ) {
16618         /* White made time control */
16619         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16620         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16621         /* [HGM] time odds: correct new time quota for time odds! */
16622                                             / WhitePlayer()->timeOdds;
16623         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16624     } else {
16625         lastBlack -= blackTimeRemaining;
16626         /* Black made time control */
16627         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16628                                             / WhitePlayer()->other->timeOdds;
16629         lastWhite = whiteTimeRemaining;
16630     }
16631 }
16632
16633 void
16634 DisplayBothClocks ()
16635 {
16636     int wom = gameMode == EditPosition ?
16637       !blackPlaysFirst : WhiteOnMove(currentMove);
16638     DisplayWhiteClock(whiteTimeRemaining, wom);
16639     DisplayBlackClock(blackTimeRemaining, !wom);
16640 }
16641
16642
16643 /* Timekeeping seems to be a portability nightmare.  I think everyone
16644    has ftime(), but I'm really not sure, so I'm including some ifdefs
16645    to use other calls if you don't.  Clocks will be less accurate if
16646    you have neither ftime nor gettimeofday.
16647 */
16648
16649 /* VS 2008 requires the #include outside of the function */
16650 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16651 #include <sys/timeb.h>
16652 #endif
16653
16654 /* Get the current time as a TimeMark */
16655 void
16656 GetTimeMark (TimeMark *tm)
16657 {
16658 #if HAVE_GETTIMEOFDAY
16659
16660     struct timeval timeVal;
16661     struct timezone timeZone;
16662
16663     gettimeofday(&timeVal, &timeZone);
16664     tm->sec = (long) timeVal.tv_sec;
16665     tm->ms = (int) (timeVal.tv_usec / 1000L);
16666
16667 #else /*!HAVE_GETTIMEOFDAY*/
16668 #if HAVE_FTIME
16669
16670 // include <sys/timeb.h> / moved to just above start of function
16671     struct timeb timeB;
16672
16673     ftime(&timeB);
16674     tm->sec = (long) timeB.time;
16675     tm->ms = (int) timeB.millitm;
16676
16677 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16678     tm->sec = (long) time(NULL);
16679     tm->ms = 0;
16680 #endif
16681 #endif
16682 }
16683
16684 /* Return the difference in milliseconds between two
16685    time marks.  We assume the difference will fit in a long!
16686 */
16687 long
16688 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16689 {
16690     return 1000L*(tm2->sec - tm1->sec) +
16691            (long) (tm2->ms - tm1->ms);
16692 }
16693
16694
16695 /*
16696  * Code to manage the game clocks.
16697  *
16698  * In tournament play, black starts the clock and then white makes a move.
16699  * We give the human user a slight advantage if he is playing white---the
16700  * clocks don't run until he makes his first move, so it takes zero time.
16701  * Also, we don't account for network lag, so we could get out of sync
16702  * with GNU Chess's clock -- but then, referees are always right.
16703  */
16704
16705 static TimeMark tickStartTM;
16706 static long intendedTickLength;
16707
16708 long
16709 NextTickLength (long timeRemaining)
16710 {
16711     long nominalTickLength, nextTickLength;
16712
16713     if (timeRemaining > 0L && timeRemaining <= 10000L)
16714       nominalTickLength = 100L;
16715     else
16716       nominalTickLength = 1000L;
16717     nextTickLength = timeRemaining % nominalTickLength;
16718     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16719
16720     return nextTickLength;
16721 }
16722
16723 /* Adjust clock one minute up or down */
16724 void
16725 AdjustClock (Boolean which, int dir)
16726 {
16727     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16728     if(which) blackTimeRemaining += 60000*dir;
16729     else      whiteTimeRemaining += 60000*dir;
16730     DisplayBothClocks();
16731     adjustedClock = TRUE;
16732 }
16733
16734 /* Stop clocks and reset to a fresh time control */
16735 void
16736 ResetClocks ()
16737 {
16738     (void) StopClockTimer();
16739     if (appData.icsActive) {
16740         whiteTimeRemaining = blackTimeRemaining = 0;
16741     } else if (searchTime) {
16742         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16743         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16744     } else { /* [HGM] correct new time quote for time odds */
16745         whiteTC = blackTC = fullTimeControlString;
16746         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16747         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16748     }
16749     if (whiteFlag || blackFlag) {
16750         DisplayTitle("");
16751         whiteFlag = blackFlag = FALSE;
16752     }
16753     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16754     DisplayBothClocks();
16755     adjustedClock = FALSE;
16756 }
16757
16758 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16759
16760 /* Decrement running clock by amount of time that has passed */
16761 void
16762 DecrementClocks ()
16763 {
16764     long timeRemaining;
16765     long lastTickLength, fudge;
16766     TimeMark now;
16767
16768     if (!appData.clockMode) return;
16769     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16770
16771     GetTimeMark(&now);
16772
16773     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16774
16775     /* Fudge if we woke up a little too soon */
16776     fudge = intendedTickLength - lastTickLength;
16777     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16778
16779     if (WhiteOnMove(forwardMostMove)) {
16780         if(whiteNPS >= 0) lastTickLength = 0;
16781         timeRemaining = whiteTimeRemaining -= lastTickLength;
16782         if(timeRemaining < 0 && !appData.icsActive) {
16783             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16784             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16785                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16786                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16787             }
16788         }
16789         DisplayWhiteClock(whiteTimeRemaining - fudge,
16790                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16791     } else {
16792         if(blackNPS >= 0) lastTickLength = 0;
16793         timeRemaining = blackTimeRemaining -= lastTickLength;
16794         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16795             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16796             if(suddenDeath) {
16797                 blackStartMove = forwardMostMove;
16798                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16799             }
16800         }
16801         DisplayBlackClock(blackTimeRemaining - fudge,
16802                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16803     }
16804     if (CheckFlags()) return;
16805
16806     if(twoBoards) { // count down secondary board's clocks as well
16807         activePartnerTime -= lastTickLength;
16808         partnerUp = 1;
16809         if(activePartner == 'W')
16810             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16811         else
16812             DisplayBlackClock(activePartnerTime, TRUE);
16813         partnerUp = 0;
16814     }
16815
16816     tickStartTM = now;
16817     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16818     StartClockTimer(intendedTickLength);
16819
16820     /* if the time remaining has fallen below the alarm threshold, sound the
16821      * alarm. if the alarm has sounded and (due to a takeback or time control
16822      * with increment) the time remaining has increased to a level above the
16823      * threshold, reset the alarm so it can sound again.
16824      */
16825
16826     if (appData.icsActive && appData.icsAlarm) {
16827
16828         /* make sure we are dealing with the user's clock */
16829         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16830                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16831            )) return;
16832
16833         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16834             alarmSounded = FALSE;
16835         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16836             PlayAlarmSound();
16837             alarmSounded = TRUE;
16838         }
16839     }
16840 }
16841
16842
16843 /* A player has just moved, so stop the previously running
16844    clock and (if in clock mode) start the other one.
16845    We redisplay both clocks in case we're in ICS mode, because
16846    ICS gives us an update to both clocks after every move.
16847    Note that this routine is called *after* forwardMostMove
16848    is updated, so the last fractional tick must be subtracted
16849    from the color that is *not* on move now.
16850 */
16851 void
16852 SwitchClocks (int newMoveNr)
16853 {
16854     long lastTickLength;
16855     TimeMark now;
16856     int flagged = FALSE;
16857
16858     GetTimeMark(&now);
16859
16860     if (StopClockTimer() && appData.clockMode) {
16861         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16862         if (!WhiteOnMove(forwardMostMove)) {
16863             if(blackNPS >= 0) lastTickLength = 0;
16864             blackTimeRemaining -= lastTickLength;
16865            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16866 //         if(pvInfoList[forwardMostMove].time == -1)
16867                  pvInfoList[forwardMostMove].time =               // use GUI time
16868                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16869         } else {
16870            if(whiteNPS >= 0) lastTickLength = 0;
16871            whiteTimeRemaining -= lastTickLength;
16872            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16873 //         if(pvInfoList[forwardMostMove].time == -1)
16874                  pvInfoList[forwardMostMove].time =
16875                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16876         }
16877         flagged = CheckFlags();
16878     }
16879     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16880     CheckTimeControl();
16881
16882     if (flagged || !appData.clockMode) return;
16883
16884     switch (gameMode) {
16885       case MachinePlaysBlack:
16886       case MachinePlaysWhite:
16887       case BeginningOfGame:
16888         if (pausing) return;
16889         break;
16890
16891       case EditGame:
16892       case PlayFromGameFile:
16893       case IcsExamining:
16894         return;
16895
16896       default:
16897         break;
16898     }
16899
16900     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16901         if(WhiteOnMove(forwardMostMove))
16902              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16903         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16904     }
16905
16906     tickStartTM = now;
16907     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16908       whiteTimeRemaining : blackTimeRemaining);
16909     StartClockTimer(intendedTickLength);
16910 }
16911
16912
16913 /* Stop both clocks */
16914 void
16915 StopClocks ()
16916 {
16917     long lastTickLength;
16918     TimeMark now;
16919
16920     if (!StopClockTimer()) return;
16921     if (!appData.clockMode) return;
16922
16923     GetTimeMark(&now);
16924
16925     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16926     if (WhiteOnMove(forwardMostMove)) {
16927         if(whiteNPS >= 0) lastTickLength = 0;
16928         whiteTimeRemaining -= lastTickLength;
16929         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16930     } else {
16931         if(blackNPS >= 0) lastTickLength = 0;
16932         blackTimeRemaining -= lastTickLength;
16933         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16934     }
16935     CheckFlags();
16936 }
16937
16938 /* Start clock of player on move.  Time may have been reset, so
16939    if clock is already running, stop and restart it. */
16940 void
16941 StartClocks ()
16942 {
16943     (void) StopClockTimer(); /* in case it was running already */
16944     DisplayBothClocks();
16945     if (CheckFlags()) return;
16946
16947     if (!appData.clockMode) return;
16948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16949
16950     GetTimeMark(&tickStartTM);
16951     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16952       whiteTimeRemaining : blackTimeRemaining);
16953
16954    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16955     whiteNPS = blackNPS = -1;
16956     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16957        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16958         whiteNPS = first.nps;
16959     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16960        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16961         blackNPS = first.nps;
16962     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16963         whiteNPS = second.nps;
16964     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16965         blackNPS = second.nps;
16966     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16967
16968     StartClockTimer(intendedTickLength);
16969 }
16970
16971 char *
16972 TimeString (long ms)
16973 {
16974     long second, minute, hour, day;
16975     char *sign = "";
16976     static char buf[32];
16977
16978     if (ms > 0 && ms <= 9900) {
16979       /* convert milliseconds to tenths, rounding up */
16980       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16981
16982       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16983       return buf;
16984     }
16985
16986     /* convert milliseconds to seconds, rounding up */
16987     /* use floating point to avoid strangeness of integer division
16988        with negative dividends on many machines */
16989     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16990
16991     if (second < 0) {
16992         sign = "-";
16993         second = -second;
16994     }
16995
16996     day = second / (60 * 60 * 24);
16997     second = second % (60 * 60 * 24);
16998     hour = second / (60 * 60);
16999     second = second % (60 * 60);
17000     minute = second / 60;
17001     second = second % 60;
17002
17003     if (day > 0)
17004       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17005               sign, day, hour, minute, second);
17006     else if (hour > 0)
17007       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17008     else
17009       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17010
17011     return buf;
17012 }
17013
17014
17015 /*
17016  * This is necessary because some C libraries aren't ANSI C compliant yet.
17017  */
17018 char *
17019 StrStr (char *string, char *match)
17020 {
17021     int i, length;
17022
17023     length = strlen(match);
17024
17025     for (i = strlen(string) - length; i >= 0; i--, string++)
17026       if (!strncmp(match, string, length))
17027         return string;
17028
17029     return NULL;
17030 }
17031
17032 char *
17033 StrCaseStr (char *string, char *match)
17034 {
17035     int i, j, length;
17036
17037     length = strlen(match);
17038
17039     for (i = strlen(string) - length; i >= 0; i--, string++) {
17040         for (j = 0; j < length; j++) {
17041             if (ToLower(match[j]) != ToLower(string[j]))
17042               break;
17043         }
17044         if (j == length) return string;
17045     }
17046
17047     return NULL;
17048 }
17049
17050 #ifndef _amigados
17051 int
17052 StrCaseCmp (char *s1, char *s2)
17053 {
17054     char c1, c2;
17055
17056     for (;;) {
17057         c1 = ToLower(*s1++);
17058         c2 = ToLower(*s2++);
17059         if (c1 > c2) return 1;
17060         if (c1 < c2) return -1;
17061         if (c1 == NULLCHAR) return 0;
17062     }
17063 }
17064
17065
17066 int
17067 ToLower (int c)
17068 {
17069     return isupper(c) ? tolower(c) : c;
17070 }
17071
17072
17073 int
17074 ToUpper (int c)
17075 {
17076     return islower(c) ? toupper(c) : c;
17077 }
17078 #endif /* !_amigados    */
17079
17080 char *
17081 StrSave (char *s)
17082 {
17083   char *ret;
17084
17085   if ((ret = (char *) malloc(strlen(s) + 1)))
17086     {
17087       safeStrCpy(ret, s, strlen(s)+1);
17088     }
17089   return ret;
17090 }
17091
17092 char *
17093 StrSavePtr (char *s, char **savePtr)
17094 {
17095     if (*savePtr) {
17096         free(*savePtr);
17097     }
17098     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17099       safeStrCpy(*savePtr, s, strlen(s)+1);
17100     }
17101     return(*savePtr);
17102 }
17103
17104 char *
17105 PGNDate ()
17106 {
17107     time_t clock;
17108     struct tm *tm;
17109     char buf[MSG_SIZ];
17110
17111     clock = time((time_t *)NULL);
17112     tm = localtime(&clock);
17113     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17114             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17115     return StrSave(buf);
17116 }
17117
17118
17119 char *
17120 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17121 {
17122     int i, j, fromX, fromY, toX, toY;
17123     int whiteToPlay;
17124     char buf[MSG_SIZ];
17125     char *p, *q;
17126     int emptycount;
17127     ChessSquare piece;
17128
17129     whiteToPlay = (gameMode == EditPosition) ?
17130       !blackPlaysFirst : (move % 2 == 0);
17131     p = buf;
17132
17133     /* Piece placement data */
17134     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17135         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17136         emptycount = 0;
17137         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17138             if (boards[move][i][j] == EmptySquare) {
17139                 emptycount++;
17140             } else { ChessSquare piece = boards[move][i][j];
17141                 if (emptycount > 0) {
17142                     if(emptycount<10) /* [HGM] can be >= 10 */
17143                         *p++ = '0' + emptycount;
17144                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17145                     emptycount = 0;
17146                 }
17147                 if(PieceToChar(piece) == '+') {
17148                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17149                     *p++ = '+';
17150                     piece = (ChessSquare)(DEMOTED piece);
17151                 }
17152                 *p++ = PieceToChar(piece);
17153                 if(p[-1] == '~') {
17154                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17155                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17156                     *p++ = '~';
17157                 }
17158             }
17159         }
17160         if (emptycount > 0) {
17161             if(emptycount<10) /* [HGM] can be >= 10 */
17162                 *p++ = '0' + emptycount;
17163             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17164             emptycount = 0;
17165         }
17166         *p++ = '/';
17167     }
17168     *(p - 1) = ' ';
17169
17170     /* [HGM] print Crazyhouse or Shogi holdings */
17171     if( gameInfo.holdingsWidth ) {
17172         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17173         q = p;
17174         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17175             piece = boards[move][i][BOARD_WIDTH-1];
17176             if( piece != EmptySquare )
17177               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17178                   *p++ = PieceToChar(piece);
17179         }
17180         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17181             piece = boards[move][BOARD_HEIGHT-i-1][0];
17182             if( piece != EmptySquare )
17183               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17184                   *p++ = PieceToChar(piece);
17185         }
17186
17187         if( q == p ) *p++ = '-';
17188         *p++ = ']';
17189         *p++ = ' ';
17190     }
17191
17192     /* Active color */
17193     *p++ = whiteToPlay ? 'w' : 'b';
17194     *p++ = ' ';
17195
17196   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17197     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17198   } else {
17199   if(nrCastlingRights) {
17200      q = p;
17201      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17202        /* [HGM] write directly from rights */
17203            if(boards[move][CASTLING][2] != NoRights &&
17204               boards[move][CASTLING][0] != NoRights   )
17205                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17206            if(boards[move][CASTLING][2] != NoRights &&
17207               boards[move][CASTLING][1] != NoRights   )
17208                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17209            if(boards[move][CASTLING][5] != NoRights &&
17210               boards[move][CASTLING][3] != NoRights   )
17211                 *p++ = boards[move][CASTLING][3] + AAA;
17212            if(boards[move][CASTLING][5] != NoRights &&
17213               boards[move][CASTLING][4] != NoRights   )
17214                 *p++ = boards[move][CASTLING][4] + AAA;
17215      } else {
17216
17217         /* [HGM] write true castling rights */
17218         if( nrCastlingRights == 6 ) {
17219             int q, k=0;
17220             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17221                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17222             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17223                  boards[move][CASTLING][2] != NoRights  );
17224             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17225                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17226                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17227                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17228                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17229             }
17230             if(q) *p++ = 'Q';
17231             k = 0;
17232             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17233                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17234             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17235                  boards[move][CASTLING][5] != NoRights  );
17236             if(gameInfo.variant == VariantSChess) {
17237                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17238                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17239                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17240                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17241             }
17242             if(q) *p++ = 'q';
17243         }
17244      }
17245      if (q == p) *p++ = '-'; /* No castling rights */
17246      *p++ = ' ';
17247   }
17248
17249   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17250      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17251      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17252     /* En passant target square */
17253     if (move > backwardMostMove) {
17254         fromX = moveList[move - 1][0] - AAA;
17255         fromY = moveList[move - 1][1] - ONE;
17256         toX = moveList[move - 1][2] - AAA;
17257         toY = moveList[move - 1][3] - ONE;
17258         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17259             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17260             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17261             fromX == toX) {
17262             /* 2-square pawn move just happened */
17263             *p++ = toX + AAA;
17264             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17265         } else {
17266             *p++ = '-';
17267         }
17268     } else if(move == backwardMostMove) {
17269         // [HGM] perhaps we should always do it like this, and forget the above?
17270         if((signed char)boards[move][EP_STATUS] >= 0) {
17271             *p++ = boards[move][EP_STATUS] + AAA;
17272             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17273         } else {
17274             *p++ = '-';
17275         }
17276     } else {
17277         *p++ = '-';
17278     }
17279     *p++ = ' ';
17280   }
17281   }
17282
17283     if(moveCounts)
17284     {   int i = 0, j=move;
17285
17286         /* [HGM] find reversible plies */
17287         if (appData.debugMode) { int k;
17288             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17289             for(k=backwardMostMove; k<=forwardMostMove; k++)
17290                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17291
17292         }
17293
17294         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17295         if( j == backwardMostMove ) i += initialRulePlies;
17296         sprintf(p, "%d ", i);
17297         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17298
17299         /* Fullmove number */
17300         sprintf(p, "%d", (move / 2) + 1);
17301     } else *--p = NULLCHAR;
17302
17303     return StrSave(buf);
17304 }
17305
17306 Boolean
17307 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17308 {
17309     int i, j;
17310     char *p, c;
17311     int emptycount, virgin[BOARD_FILES];
17312     ChessSquare piece;
17313
17314     p = fen;
17315
17316     /* [HGM] by default clear Crazyhouse holdings, if present */
17317     if(gameInfo.holdingsWidth) {
17318        for(i=0; i<BOARD_HEIGHT; i++) {
17319            board[i][0]             = EmptySquare; /* black holdings */
17320            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17321            board[i][1]             = (ChessSquare) 0; /* black counts */
17322            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17323        }
17324     }
17325
17326     /* Piece placement data */
17327     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17328         j = 0;
17329         for (;;) {
17330             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17331                 if (*p == '/') p++;
17332                 emptycount = gameInfo.boardWidth - j;
17333                 while (emptycount--)
17334                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17335                 break;
17336 #if(BOARD_FILES >= 10)
17337             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17338                 p++; emptycount=10;
17339                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17340                 while (emptycount--)
17341                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17342 #endif
17343             } else if (isdigit(*p)) {
17344                 emptycount = *p++ - '0';
17345                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17346                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17347                 while (emptycount--)
17348                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17349             } else if (*p == '+' || isalpha(*p)) {
17350                 if (j >= gameInfo.boardWidth) return FALSE;
17351                 if(*p=='+') {
17352                     piece = CharToPiece(*++p);
17353                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17354                     piece = (ChessSquare) (PROMOTED piece ); p++;
17355                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17356                 } else piece = CharToPiece(*p++);
17357
17358                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17359                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17360                     piece = (ChessSquare) (PROMOTED piece);
17361                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17362                     p++;
17363                 }
17364                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17365             } else {
17366                 return FALSE;
17367             }
17368         }
17369     }
17370     while (*p == '/' || *p == ' ') p++;
17371
17372     /* [HGM] look for Crazyhouse holdings here */
17373     while(*p==' ') p++;
17374     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17375         if(*p == '[') p++;
17376         if(*p == '-' ) p++; /* empty holdings */ else {
17377             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17378             /* if we would allow FEN reading to set board size, we would   */
17379             /* have to add holdings and shift the board read so far here   */
17380             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17381                 p++;
17382                 if((int) piece >= (int) BlackPawn ) {
17383                     i = (int)piece - (int)BlackPawn;
17384                     i = PieceToNumber((ChessSquare)i);
17385                     if( i >= gameInfo.holdingsSize ) return FALSE;
17386                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17387                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17388                 } else {
17389                     i = (int)piece - (int)WhitePawn;
17390                     i = PieceToNumber((ChessSquare)i);
17391                     if( i >= gameInfo.holdingsSize ) return FALSE;
17392                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17393                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17394                 }
17395             }
17396         }
17397         if(*p == ']') p++;
17398     }
17399
17400     while(*p == ' ') p++;
17401
17402     /* Active color */
17403     c = *p++;
17404     if(appData.colorNickNames) {
17405       if( c == appData.colorNickNames[0] ) c = 'w'; else
17406       if( c == appData.colorNickNames[1] ) c = 'b';
17407     }
17408     switch (c) {
17409       case 'w':
17410         *blackPlaysFirst = FALSE;
17411         break;
17412       case 'b':
17413         *blackPlaysFirst = TRUE;
17414         break;
17415       default:
17416         return FALSE;
17417     }
17418
17419     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17420     /* return the extra info in global variiables             */
17421
17422     /* set defaults in case FEN is incomplete */
17423     board[EP_STATUS] = EP_UNKNOWN;
17424     for(i=0; i<nrCastlingRights; i++ ) {
17425         board[CASTLING][i] =
17426             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17427     }   /* assume possible unless obviously impossible */
17428     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17429     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17430     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17431                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17432     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17433     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17434     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17435                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17436     FENrulePlies = 0;
17437
17438     while(*p==' ') p++;
17439     if(nrCastlingRights) {
17440       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17441       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17442           /* castling indicator present, so default becomes no castlings */
17443           for(i=0; i<nrCastlingRights; i++ ) {
17444                  board[CASTLING][i] = NoRights;
17445           }
17446       }
17447       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17448              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17449              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17450              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17451         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17452
17453         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17454             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17455             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17456         }
17457         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17458             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17459         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17460                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17461         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17462                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17463         switch(c) {
17464           case'K':
17465               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17466               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17467               board[CASTLING][2] = whiteKingFile;
17468               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17469               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17470               break;
17471           case'Q':
17472               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17473               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17474               board[CASTLING][2] = whiteKingFile;
17475               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17476               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17477               break;
17478           case'k':
17479               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17480               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17481               board[CASTLING][5] = blackKingFile;
17482               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17483               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17484               break;
17485           case'q':
17486               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17487               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17488               board[CASTLING][5] = blackKingFile;
17489               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17490               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17491           case '-':
17492               break;
17493           default: /* FRC castlings */
17494               if(c >= 'a') { /* black rights */
17495                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17496                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17497                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17498                   if(i == BOARD_RGHT) break;
17499                   board[CASTLING][5] = i;
17500                   c -= AAA;
17501                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17502                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17503                   if(c > i)
17504                       board[CASTLING][3] = c;
17505                   else
17506                       board[CASTLING][4] = c;
17507               } else { /* white rights */
17508                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17509                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17510                     if(board[0][i] == WhiteKing) break;
17511                   if(i == BOARD_RGHT) break;
17512                   board[CASTLING][2] = i;
17513                   c -= AAA - 'a' + 'A';
17514                   if(board[0][c] >= WhiteKing) break;
17515                   if(c > i)
17516                       board[CASTLING][0] = c;
17517                   else
17518                       board[CASTLING][1] = c;
17519               }
17520         }
17521       }
17522       for(i=0; i<nrCastlingRights; i++)
17523         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17524       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17525     if (appData.debugMode) {
17526         fprintf(debugFP, "FEN castling rights:");
17527         for(i=0; i<nrCastlingRights; i++)
17528         fprintf(debugFP, " %d", board[CASTLING][i]);
17529         fprintf(debugFP, "\n");
17530     }
17531
17532       while(*p==' ') p++;
17533     }
17534
17535     /* read e.p. field in games that know e.p. capture */
17536     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17537        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17538        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17539       if(*p=='-') {
17540         p++; board[EP_STATUS] = EP_NONE;
17541       } else {
17542          char c = *p++ - AAA;
17543
17544          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17545          if(*p >= '0' && *p <='9') p++;
17546          board[EP_STATUS] = c;
17547       }
17548     }
17549
17550
17551     if(sscanf(p, "%d", &i) == 1) {
17552         FENrulePlies = i; /* 50-move ply counter */
17553         /* (The move number is still ignored)    */
17554     }
17555
17556     return TRUE;
17557 }
17558
17559 void
17560 EditPositionPasteFEN (char *fen)
17561 {
17562   if (fen != NULL) {
17563     Board initial_position;
17564
17565     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17566       DisplayError(_("Bad FEN position in clipboard"), 0);
17567       return ;
17568     } else {
17569       int savedBlackPlaysFirst = blackPlaysFirst;
17570       EditPositionEvent();
17571       blackPlaysFirst = savedBlackPlaysFirst;
17572       CopyBoard(boards[0], initial_position);
17573       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17574       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17575       DisplayBothClocks();
17576       DrawPosition(FALSE, boards[currentMove]);
17577     }
17578   }
17579 }
17580
17581 static char cseq[12] = "\\   ";
17582
17583 Boolean
17584 set_cont_sequence (char *new_seq)
17585 {
17586     int len;
17587     Boolean ret;
17588
17589     // handle bad attempts to set the sequence
17590         if (!new_seq)
17591                 return 0; // acceptable error - no debug
17592
17593     len = strlen(new_seq);
17594     ret = (len > 0) && (len < sizeof(cseq));
17595     if (ret)
17596       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17597     else if (appData.debugMode)
17598       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17599     return ret;
17600 }
17601
17602 /*
17603     reformat a source message so words don't cross the width boundary.  internal
17604     newlines are not removed.  returns the wrapped size (no null character unless
17605     included in source message).  If dest is NULL, only calculate the size required
17606     for the dest buffer.  lp argument indicats line position upon entry, and it's
17607     passed back upon exit.
17608 */
17609 int
17610 wrap (char *dest, char *src, int count, int width, int *lp)
17611 {
17612     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17613
17614     cseq_len = strlen(cseq);
17615     old_line = line = *lp;
17616     ansi = len = clen = 0;
17617
17618     for (i=0; i < count; i++)
17619     {
17620         if (src[i] == '\033')
17621             ansi = 1;
17622
17623         // if we hit the width, back up
17624         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17625         {
17626             // store i & len in case the word is too long
17627             old_i = i, old_len = len;
17628
17629             // find the end of the last word
17630             while (i && src[i] != ' ' && src[i] != '\n')
17631             {
17632                 i--;
17633                 len--;
17634             }
17635
17636             // word too long?  restore i & len before splitting it
17637             if ((old_i-i+clen) >= width)
17638             {
17639                 i = old_i;
17640                 len = old_len;
17641             }
17642
17643             // extra space?
17644             if (i && src[i-1] == ' ')
17645                 len--;
17646
17647             if (src[i] != ' ' && src[i] != '\n')
17648             {
17649                 i--;
17650                 if (len)
17651                     len--;
17652             }
17653
17654             // now append the newline and continuation sequence
17655             if (dest)
17656                 dest[len] = '\n';
17657             len++;
17658             if (dest)
17659                 strncpy(dest+len, cseq, cseq_len);
17660             len += cseq_len;
17661             line = cseq_len;
17662             clen = cseq_len;
17663             continue;
17664         }
17665
17666         if (dest)
17667             dest[len] = src[i];
17668         len++;
17669         if (!ansi)
17670             line++;
17671         if (src[i] == '\n')
17672             line = 0;
17673         if (src[i] == 'm')
17674             ansi = 0;
17675     }
17676     if (dest && appData.debugMode)
17677     {
17678         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17679             count, width, line, len, *lp);
17680         show_bytes(debugFP, src, count);
17681         fprintf(debugFP, "\ndest: ");
17682         show_bytes(debugFP, dest, len);
17683         fprintf(debugFP, "\n");
17684     }
17685     *lp = dest ? line : old_line;
17686
17687     return len;
17688 }
17689
17690 // [HGM] vari: routines for shelving variations
17691 Boolean modeRestore = FALSE;
17692
17693 void
17694 PushInner (int firstMove, int lastMove)
17695 {
17696         int i, j, nrMoves = lastMove - firstMove;
17697
17698         // push current tail of game on stack
17699         savedResult[storedGames] = gameInfo.result;
17700         savedDetails[storedGames] = gameInfo.resultDetails;
17701         gameInfo.resultDetails = NULL;
17702         savedFirst[storedGames] = firstMove;
17703         savedLast [storedGames] = lastMove;
17704         savedFramePtr[storedGames] = framePtr;
17705         framePtr -= nrMoves; // reserve space for the boards
17706         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17707             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17708             for(j=0; j<MOVE_LEN; j++)
17709                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17710             for(j=0; j<2*MOVE_LEN; j++)
17711                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17712             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17713             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17714             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17715             pvInfoList[firstMove+i-1].depth = 0;
17716             commentList[framePtr+i] = commentList[firstMove+i];
17717             commentList[firstMove+i] = NULL;
17718         }
17719
17720         storedGames++;
17721         forwardMostMove = firstMove; // truncate game so we can start variation
17722 }
17723
17724 void
17725 PushTail (int firstMove, int lastMove)
17726 {
17727         if(appData.icsActive) { // only in local mode
17728                 forwardMostMove = currentMove; // mimic old ICS behavior
17729                 return;
17730         }
17731         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17732
17733         PushInner(firstMove, lastMove);
17734         if(storedGames == 1) GreyRevert(FALSE);
17735         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17736 }
17737
17738 void
17739 PopInner (Boolean annotate)
17740 {
17741         int i, j, nrMoves;
17742         char buf[8000], moveBuf[20];
17743
17744         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17745         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17746         nrMoves = savedLast[storedGames] - currentMove;
17747         if(annotate) {
17748                 int cnt = 10;
17749                 if(!WhiteOnMove(currentMove))
17750                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17751                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17752                 for(i=currentMove; i<forwardMostMove; i++) {
17753                         if(WhiteOnMove(i))
17754                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17755                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17756                         strcat(buf, moveBuf);
17757                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17758                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17759                 }
17760                 strcat(buf, ")");
17761         }
17762         for(i=1; i<=nrMoves; i++) { // copy last variation back
17763             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17764             for(j=0; j<MOVE_LEN; j++)
17765                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17766             for(j=0; j<2*MOVE_LEN; j++)
17767                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17768             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17769             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17770             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17771             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17772             commentList[currentMove+i] = commentList[framePtr+i];
17773             commentList[framePtr+i] = NULL;
17774         }
17775         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17776         framePtr = savedFramePtr[storedGames];
17777         gameInfo.result = savedResult[storedGames];
17778         if(gameInfo.resultDetails != NULL) {
17779             free(gameInfo.resultDetails);
17780       }
17781         gameInfo.resultDetails = savedDetails[storedGames];
17782         forwardMostMove = currentMove + nrMoves;
17783 }
17784
17785 Boolean
17786 PopTail (Boolean annotate)
17787 {
17788         if(appData.icsActive) return FALSE; // only in local mode
17789         if(!storedGames) return FALSE; // sanity
17790         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17791
17792         PopInner(annotate);
17793         if(currentMove < forwardMostMove) ForwardEvent(); else
17794         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17795
17796         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17797         return TRUE;
17798 }
17799
17800 void
17801 CleanupTail ()
17802 {       // remove all shelved variations
17803         int i;
17804         for(i=0; i<storedGames; i++) {
17805             if(savedDetails[i])
17806                 free(savedDetails[i]);
17807             savedDetails[i] = NULL;
17808         }
17809         for(i=framePtr; i<MAX_MOVES; i++) {
17810                 if(commentList[i]) free(commentList[i]);
17811                 commentList[i] = NULL;
17812         }
17813         framePtr = MAX_MOVES-1;
17814         storedGames = 0;
17815 }
17816
17817 void
17818 LoadVariation (int index, char *text)
17819 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17820         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17821         int level = 0, move;
17822
17823         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17824         // first find outermost bracketing variation
17825         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17826             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17827                 if(*p == '{') wait = '}'; else
17828                 if(*p == '[') wait = ']'; else
17829                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17830                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17831             }
17832             if(*p == wait) wait = NULLCHAR; // closing ]} found
17833             p++;
17834         }
17835         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17836         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17837         end[1] = NULLCHAR; // clip off comment beyond variation
17838         ToNrEvent(currentMove-1);
17839         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17840         // kludge: use ParsePV() to append variation to game
17841         move = currentMove;
17842         ParsePV(start, TRUE, TRUE);
17843         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17844         ClearPremoveHighlights();
17845         CommentPopDown();
17846         ToNrEvent(currentMove+1);
17847 }
17848
17849 void
17850 LoadTheme ()
17851 {
17852     char *p, *q, buf[MSG_SIZ];
17853     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17854         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17855         ParseArgsFromString(buf);
17856         ActivateTheme(TRUE); // also redo colors
17857         return;
17858     }
17859     p = nickName;
17860     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17861     {
17862         int len;
17863         q = appData.themeNames;
17864         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17865       if(appData.useBitmaps) {
17866         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17867                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17868                 appData.liteBackTextureMode,
17869                 appData.darkBackTextureMode );
17870       } else {
17871         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17872                 Col2Text(2),   // lightSquareColor
17873                 Col2Text(3) ); // darkSquareColor
17874       }
17875       if(appData.useBorder) {
17876         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17877                 appData.border);
17878       } else {
17879         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17880       }
17881       if(appData.useFont) {
17882         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17883                 appData.renderPiecesWithFont,
17884                 appData.fontToPieceTable,
17885                 Col2Text(9),    // appData.fontBackColorWhite
17886                 Col2Text(10) ); // appData.fontForeColorBlack
17887       } else {
17888         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17889                 appData.pieceDirectory);
17890         if(!appData.pieceDirectory[0])
17891           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17892                 Col2Text(0),   // whitePieceColor
17893                 Col2Text(1) ); // blackPieceColor
17894       }
17895       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17896                 Col2Text(4),   // highlightSquareColor
17897                 Col2Text(5) ); // premoveHighlightColor
17898         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17899         if(insert != q) insert[-1] = NULLCHAR;
17900         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17901         if(q)   free(q);
17902     }
17903     ActivateTheme(FALSE);
17904 }