Implement hover command
[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 >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7029             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7030             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7031             if(*fen == 'T') marker[r][f++] = 0; else
7032             if(*fen == 'Y') marker[r][f++] = 1; else
7033             if(*fen == 'G') marker[r][f++] = 3; else
7034             if(*fen == 'B') marker[r][f++] = 4; else
7035             if(*fen == 'C') marker[r][f++] = 5; else
7036             if(*fen == 'M') marker[r][f++] = 6; else
7037             if(*fen == 'W') marker[r][f++] = 7; else
7038             if(*fen == 'D') marker[r][f++] = 8; else
7039             if(*fen == 'R') marker[r][f++] = 2; else {
7040                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7041               f += s; fen -= s>0;
7042             }
7043             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7044             if(r < 0) break;
7045             fen++;
7046         }
7047         DrawPosition(TRUE, NULL);
7048 }
7049
7050 void
7051 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7052 {
7053     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7054     Markers *m = (Markers *) closure;
7055     if(rf == fromY && ff == fromX)
7056         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7057                          || kind == WhiteCapturesEnPassant
7058                          || kind == BlackCapturesEnPassant);
7059     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7060 }
7061
7062 void
7063 MarkTargetSquares (int clear)
7064 {
7065   int x, y, sum=0;
7066   if(clear) { // no reason to ever suppress clearing
7067     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7068     if(!sum) return; // nothing was cleared,no redraw needed
7069   } else {
7070     int capt = 0;
7071     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7072        !appData.testLegality || gameMode == EditPosition) return;
7073     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7074     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7075       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7076       if(capt)
7077       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7078     }
7079   }
7080   DrawPosition(FALSE, NULL);
7081 }
7082
7083 int
7084 Explode (Board board, int fromX, int fromY, int toX, int toY)
7085 {
7086     if(gameInfo.variant == VariantAtomic &&
7087        (board[toY][toX] != EmptySquare ||                     // capture?
7088         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7089                          board[fromY][fromX] == BlackPawn   )
7090       )) {
7091         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7092         return TRUE;
7093     }
7094     return FALSE;
7095 }
7096
7097 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7098
7099 int
7100 CanPromote (ChessSquare piece, int y)
7101 {
7102         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7103         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7104         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7105            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7106            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7107          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7108         return (piece == BlackPawn && y == 1 ||
7109                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7110                 piece == BlackLance && y == 1 ||
7111                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7112 }
7113
7114 void
7115 HoverEvent (int hiX, int hiY, int x, int y)
7116 {
7117         static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7118         int r, f;
7119         if(!first.highlight) return;
7120         if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
7121           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7122             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7123         else if(hiX != x || hiY != y) {
7124           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7125           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7126             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7127           if(marker[y][x] == 2 && legal[y][x] == 1) {
7128             char buf[MSG_SIZ];
7129             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7130             SendToProgram(buf, &first);
7131           }
7132           SetHighlights(fromX, fromY, x, y);
7133         }
7134 }
7135
7136 void ReportClick(char *action, int x, int y)
7137 {
7138         char buf[MSG_SIZ]; // Inform engine of what user does
7139         int r, f;
7140         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7141           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7142         if(!first.highlight || gameMode == EditPosition) return;
7143         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7144         SendToProgram(buf, &first);
7145 }
7146
7147 void
7148 LeftClick (ClickType clickType, int xPix, int yPix)
7149 {
7150     int x, y;
7151     Boolean saveAnimate;
7152     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7153     char promoChoice = NULLCHAR;
7154     ChessSquare piece;
7155     static TimeMark lastClickTime, prevClickTime;
7156
7157     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7158
7159     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7160
7161     if (clickType == Press) ErrorPopDown();
7162
7163     x = EventToSquare(xPix, BOARD_WIDTH);
7164     y = EventToSquare(yPix, BOARD_HEIGHT);
7165     if (!flipView && y >= 0) {
7166         y = BOARD_HEIGHT - 1 - y;
7167     }
7168     if (flipView && x >= 0) {
7169         x = BOARD_WIDTH - 1 - x;
7170     }
7171
7172     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7173         defaultPromoChoice = promoSweep;
7174         promoSweep = EmptySquare;   // terminate sweep
7175         promoDefaultAltered = TRUE;
7176         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7177     }
7178
7179     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7180         if(clickType == Release) return; // ignore upclick of click-click destination
7181         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7182         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7183         if(gameInfo.holdingsWidth &&
7184                 (WhiteOnMove(currentMove)
7185                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7186                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7187             // click in right holdings, for determining promotion piece
7188             ChessSquare p = boards[currentMove][y][x];
7189             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7190             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7191             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7192                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7193                 fromX = fromY = -1;
7194                 return;
7195             }
7196         }
7197         DrawPosition(FALSE, boards[currentMove]);
7198         return;
7199     }
7200
7201     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7202     if(clickType == Press
7203             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7204               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7205               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7206         return;
7207
7208     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7209         // could be static click on premove from-square: abort premove
7210         gotPremove = 0;
7211         ClearPremoveHighlights();
7212     }
7213
7214     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7215         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7216
7217     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7218         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7219                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7220         defaultPromoChoice = DefaultPromoChoice(side);
7221     }
7222
7223     autoQueen = appData.alwaysPromoteToQueen;
7224
7225     if (fromX == -1) {
7226       int originalY = y;
7227       gatingPiece = EmptySquare;
7228       if (clickType != Press) {
7229         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7230             DragPieceEnd(xPix, yPix); dragging = 0;
7231             DrawPosition(FALSE, NULL);
7232         }
7233         return;
7234       }
7235       doubleClick = FALSE;
7236       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7237         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7238       }
7239       fromX = x; fromY = y; toX = toY = -1;
7240       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7241          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7242          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7243             /* First square */
7244             if (OKToStartUserMove(fromX, fromY)) {
7245                 second = 0;
7246                 ReportClick("lift", x, y);
7247                 MarkTargetSquares(0);
7248                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7249                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7250                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7251                     promoSweep = defaultPromoChoice;
7252                     selectFlag = 0; lastX = xPix; lastY = yPix;
7253                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7254                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7255                 }
7256                 if (appData.highlightDragging) {
7257                     SetHighlights(fromX, fromY, -1, -1);
7258                 } else {
7259                     ClearHighlights();
7260                 }
7261             } else fromX = fromY = -1;
7262             return;
7263         }
7264     }
7265
7266     /* fromX != -1 */
7267     if (clickType == Press && gameMode != EditPosition) {
7268         ChessSquare fromP;
7269         ChessSquare toP;
7270         int frc;
7271
7272         // ignore off-board to clicks
7273         if(y < 0 || x < 0) return;
7274
7275         /* Check if clicking again on the same color piece */
7276         fromP = boards[currentMove][fromY][fromX];
7277         toP = boards[currentMove][y][x];
7278         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7279         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7280              WhitePawn <= toP && toP <= WhiteKing &&
7281              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7282              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7283             (BlackPawn <= fromP && fromP <= BlackKing &&
7284              BlackPawn <= toP && toP <= BlackKing &&
7285              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7286              !(fromP == BlackKing && toP == BlackRook && frc))) {
7287             /* Clicked again on same color piece -- changed his mind */
7288             second = (x == fromX && y == fromY);
7289             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7290                 second = FALSE; // first double-click rather than scond click
7291                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7292             }
7293             promoDefaultAltered = FALSE;
7294             MarkTargetSquares(1);
7295            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7296             if (appData.highlightDragging) {
7297                 SetHighlights(x, y, -1, -1);
7298             } else {
7299                 ClearHighlights();
7300             }
7301             if (OKToStartUserMove(x, y)) {
7302                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7303                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7304                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7305                  gatingPiece = boards[currentMove][fromY][fromX];
7306                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7307                 fromX = x;
7308                 fromY = y; dragging = 1;
7309                 ReportClick("lift", x, y);
7310                 MarkTargetSquares(0);
7311                 DragPieceBegin(xPix, yPix, FALSE);
7312                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7313                     promoSweep = defaultPromoChoice;
7314                     selectFlag = 0; lastX = xPix; lastY = yPix;
7315                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7316                 }
7317             }
7318            }
7319            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7320            second = FALSE;
7321         }
7322         // ignore clicks on holdings
7323         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7324     }
7325
7326     if (clickType == Release && x == fromX && y == fromY) {
7327         DragPieceEnd(xPix, yPix); dragging = 0;
7328         if(clearFlag) {
7329             // a deferred attempt to click-click move an empty square on top of a piece
7330             boards[currentMove][y][x] = EmptySquare;
7331             ClearHighlights();
7332             DrawPosition(FALSE, boards[currentMove]);
7333             fromX = fromY = -1; clearFlag = 0;
7334             return;
7335         }
7336         if (appData.animateDragging) {
7337             /* Undo animation damage if any */
7338             DrawPosition(FALSE, NULL);
7339         }
7340         if (second || sweepSelecting) {
7341             /* Second up/down in same square; just abort move */
7342             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7343             second = sweepSelecting = 0;
7344             fromX = fromY = -1;
7345             gatingPiece = EmptySquare;
7346             MarkTargetSquares(1);
7347             ClearHighlights();
7348             gotPremove = 0;
7349             ClearPremoveHighlights();
7350         } else {
7351             /* First upclick in same square; start click-click mode */
7352             SetHighlights(x, y, -1, -1);
7353         }
7354         return;
7355     }
7356
7357     clearFlag = 0;
7358
7359     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x]) {
7360         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7361         DisplayMessage(_("only marked squares are legal"),"");
7362         DrawPosition(TRUE, NULL);
7363         return; // ignore to-click
7364     }
7365
7366     /* we now have a different from- and (possibly off-board) to-square */
7367     /* Completed move */
7368     if(!sweepSelecting) {
7369         toX = x;
7370         toY = y;
7371     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7372
7373     saveAnimate = appData.animate;
7374     if (clickType == Press) {
7375         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7376             // must be Edit Position mode with empty-square selected
7377             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7378             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7379             return;
7380         }
7381         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7382           if(appData.sweepSelect) {
7383             ChessSquare piece = boards[currentMove][fromY][fromX];
7384             promoSweep = defaultPromoChoice;
7385             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7386             selectFlag = 0; lastX = xPix; lastY = yPix;
7387             Sweep(0); // Pawn that is going to promote: preview promotion piece
7388             sweepSelecting = 1;
7389             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7390             MarkTargetSquares(1);
7391           }
7392           return; // promo popup appears on up-click
7393         }
7394         /* Finish clickclick move */
7395         if (appData.animate || appData.highlightLastMove) {
7396             SetHighlights(fromX, fromY, toX, toY);
7397         } else {
7398             ClearHighlights();
7399         }
7400     } else {
7401 #if 0
7402 // [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
7403         /* Finish drag move */
7404         if (appData.highlightLastMove) {
7405             SetHighlights(fromX, fromY, toX, toY);
7406         } else {
7407             ClearHighlights();
7408         }
7409 #endif
7410         DragPieceEnd(xPix, yPix); dragging = 0;
7411         /* Don't animate move and drag both */
7412         appData.animate = FALSE;
7413     }
7414
7415     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7416     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7417         ChessSquare piece = boards[currentMove][fromY][fromX];
7418         if(gameMode == EditPosition && piece != EmptySquare &&
7419            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7420             int n;
7421
7422             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7423                 n = PieceToNumber(piece - (int)BlackPawn);
7424                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7425                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7426                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7427             } else
7428             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7429                 n = PieceToNumber(piece);
7430                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7431                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7432                 boards[currentMove][n][BOARD_WIDTH-2]++;
7433             }
7434             boards[currentMove][fromY][fromX] = EmptySquare;
7435         }
7436         ClearHighlights();
7437         fromX = fromY = -1;
7438         MarkTargetSquares(1);
7439         DrawPosition(TRUE, boards[currentMove]);
7440         return;
7441     }
7442
7443     // off-board moves should not be highlighted
7444     if(x < 0 || y < 0) ClearHighlights();
7445     else ReportClick("put", x, y);
7446
7447     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7448
7449     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7450         SetHighlights(fromX, fromY, toX, toY);
7451         MarkTargetSquares(1);
7452         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7453             // [HGM] super: promotion to captured piece selected from holdings
7454             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7455             promotionChoice = TRUE;
7456             // kludge follows to temporarily execute move on display, without promoting yet
7457             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7458             boards[currentMove][toY][toX] = p;
7459             DrawPosition(FALSE, boards[currentMove]);
7460             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7461             boards[currentMove][toY][toX] = q;
7462             DisplayMessage("Click in holdings to choose piece", "");
7463             return;
7464         }
7465         PromotionPopUp();
7466     } else {
7467         int oldMove = currentMove;
7468         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7469         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7470         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7471         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7472            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7473             DrawPosition(TRUE, boards[currentMove]);
7474         MarkTargetSquares(1);
7475         fromX = fromY = -1;
7476     }
7477     appData.animate = saveAnimate;
7478     if (appData.animate || appData.animateDragging) {
7479         /* Undo animation damage if needed */
7480         DrawPosition(FALSE, NULL);
7481     }
7482 }
7483
7484 int
7485 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7486 {   // front-end-free part taken out of PieceMenuPopup
7487     int whichMenu; int xSqr, ySqr;
7488
7489     if(seekGraphUp) { // [HGM] seekgraph
7490         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7491         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7492         return -2;
7493     }
7494
7495     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7496          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7497         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7498         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7499         if(action == Press)   {
7500             originalFlip = flipView;
7501             flipView = !flipView; // temporarily flip board to see game from partners perspective
7502             DrawPosition(TRUE, partnerBoard);
7503             DisplayMessage(partnerStatus, "");
7504             partnerUp = TRUE;
7505         } else if(action == Release) {
7506             flipView = originalFlip;
7507             DrawPosition(TRUE, boards[currentMove]);
7508             partnerUp = FALSE;
7509         }
7510         return -2;
7511     }
7512
7513     xSqr = EventToSquare(x, BOARD_WIDTH);
7514     ySqr = EventToSquare(y, BOARD_HEIGHT);
7515     if (action == Release) {
7516         if(pieceSweep != EmptySquare) {
7517             EditPositionMenuEvent(pieceSweep, toX, toY);
7518             pieceSweep = EmptySquare;
7519         } else UnLoadPV(); // [HGM] pv
7520     }
7521     if (action != Press) return -2; // return code to be ignored
7522     switch (gameMode) {
7523       case IcsExamining:
7524         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7525       case EditPosition:
7526         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7527         if (xSqr < 0 || ySqr < 0) return -1;
7528         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7529         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7530         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7531         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7532         NextPiece(0);
7533         return 2; // grab
7534       case IcsObserving:
7535         if(!appData.icsEngineAnalyze) return -1;
7536       case IcsPlayingWhite:
7537       case IcsPlayingBlack:
7538         if(!appData.zippyPlay) goto noZip;
7539       case AnalyzeMode:
7540       case AnalyzeFile:
7541       case MachinePlaysWhite:
7542       case MachinePlaysBlack:
7543       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7544         if (!appData.dropMenu) {
7545           LoadPV(x, y);
7546           return 2; // flag front-end to grab mouse events
7547         }
7548         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7549            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7550       case EditGame:
7551       noZip:
7552         if (xSqr < 0 || ySqr < 0) return -1;
7553         if (!appData.dropMenu || appData.testLegality &&
7554             gameInfo.variant != VariantBughouse &&
7555             gameInfo.variant != VariantCrazyhouse) return -1;
7556         whichMenu = 1; // drop menu
7557         break;
7558       default:
7559         return -1;
7560     }
7561
7562     if (((*fromX = xSqr) < 0) ||
7563         ((*fromY = ySqr) < 0)) {
7564         *fromX = *fromY = -1;
7565         return -1;
7566     }
7567     if (flipView)
7568       *fromX = BOARD_WIDTH - 1 - *fromX;
7569     else
7570       *fromY = BOARD_HEIGHT - 1 - *fromY;
7571
7572     return whichMenu;
7573 }
7574
7575 void
7576 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7577 {
7578 //    char * hint = lastHint;
7579     FrontEndProgramStats stats;
7580
7581     stats.which = cps == &first ? 0 : 1;
7582     stats.depth = cpstats->depth;
7583     stats.nodes = cpstats->nodes;
7584     stats.score = cpstats->score;
7585     stats.time = cpstats->time;
7586     stats.pv = cpstats->movelist;
7587     stats.hint = lastHint;
7588     stats.an_move_index = 0;
7589     stats.an_move_count = 0;
7590
7591     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7592         stats.hint = cpstats->move_name;
7593         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7594         stats.an_move_count = cpstats->nr_moves;
7595     }
7596
7597     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
7598
7599     SetProgramStats( &stats );
7600 }
7601
7602 void
7603 ClearEngineOutputPane (int which)
7604 {
7605     static FrontEndProgramStats dummyStats;
7606     dummyStats.which = which;
7607     dummyStats.pv = "#";
7608     SetProgramStats( &dummyStats );
7609 }
7610
7611 #define MAXPLAYERS 500
7612
7613 char *
7614 TourneyStandings (int display)
7615 {
7616     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7617     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7618     char result, *p, *names[MAXPLAYERS];
7619
7620     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7621         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7622     names[0] = p = strdup(appData.participants);
7623     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7624
7625     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7626
7627     while(result = appData.results[nr]) {
7628         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7629         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7630         wScore = bScore = 0;
7631         switch(result) {
7632           case '+': wScore = 2; break;
7633           case '-': bScore = 2; break;
7634           case '=': wScore = bScore = 1; break;
7635           case ' ':
7636           case '*': return strdup("busy"); // tourney not finished
7637         }
7638         score[w] += wScore;
7639         score[b] += bScore;
7640         games[w]++;
7641         games[b]++;
7642         nr++;
7643     }
7644     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7645     for(w=0; w<nPlayers; w++) {
7646         bScore = -1;
7647         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7648         ranking[w] = b; points[w] = bScore; score[b] = -2;
7649     }
7650     p = malloc(nPlayers*34+1);
7651     for(w=0; w<nPlayers && w<display; w++)
7652         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7653     free(names[0]);
7654     return p;
7655 }
7656
7657 void
7658 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7659 {       // count all piece types
7660         int p, f, r;
7661         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7662         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7663         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7664                 p = board[r][f];
7665                 pCnt[p]++;
7666                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7667                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7668                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7669                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7670                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7671                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7672         }
7673 }
7674
7675 int
7676 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7677 {
7678         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7679         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7680
7681         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7682         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7683         if(myPawns == 2 && nMine == 3) // KPP
7684             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7685         if(myPawns == 1 && nMine == 2) // KP
7686             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7687         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7688             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7689         if(myPawns) return FALSE;
7690         if(pCnt[WhiteRook+side])
7691             return pCnt[BlackRook-side] ||
7692                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7693                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7694                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7695         if(pCnt[WhiteCannon+side]) {
7696             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7697             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7698         }
7699         if(pCnt[WhiteKnight+side])
7700             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7701         return FALSE;
7702 }
7703
7704 int
7705 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7706 {
7707         VariantClass v = gameInfo.variant;
7708
7709         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7710         if(v == VariantShatranj) return TRUE; // always winnable through baring
7711         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7712         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7713
7714         if(v == VariantXiangqi) {
7715                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7716
7717                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7718                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7719                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7720                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7721                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7722                 if(stale) // we have at least one last-rank P plus perhaps C
7723                     return majors // KPKX
7724                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7725                 else // KCA*E*
7726                     return pCnt[WhiteFerz+side] // KCAK
7727                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7728                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7729                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7730
7731         } else if(v == VariantKnightmate) {
7732                 if(nMine == 1) return FALSE;
7733                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7734         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7735                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7736
7737                 if(nMine == 1) return FALSE; // bare King
7738                 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
7739                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7740                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7741                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7742                 if(pCnt[WhiteKnight+side])
7743                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7744                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7745                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7746                 if(nBishops)
7747                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7748                 if(pCnt[WhiteAlfil+side])
7749                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7750                 if(pCnt[WhiteWazir+side])
7751                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7752         }
7753
7754         return TRUE;
7755 }
7756
7757 int
7758 CompareWithRights (Board b1, Board b2)
7759 {
7760     int rights = 0;
7761     if(!CompareBoards(b1, b2)) return FALSE;
7762     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7763     /* compare castling rights */
7764     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7765            rights++; /* King lost rights, while rook still had them */
7766     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7767         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7768            rights++; /* but at least one rook lost them */
7769     }
7770     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7771            rights++;
7772     if( b1[CASTLING][5] != NoRights ) {
7773         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7774            rights++;
7775     }
7776     return rights == 0;
7777 }
7778
7779 int
7780 Adjudicate (ChessProgramState *cps)
7781 {       // [HGM] some adjudications useful with buggy engines
7782         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7783         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7784         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7785         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7786         int k, drop, count = 0; static int bare = 1;
7787         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7788         Boolean canAdjudicate = !appData.icsActive;
7789
7790         // most tests only when we understand the game, i.e. legality-checking on
7791             if( appData.testLegality )
7792             {   /* [HGM] Some more adjudications for obstinate engines */
7793                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7794                 static int moveCount = 6;
7795                 ChessMove result;
7796                 char *reason = NULL;
7797
7798                 /* Count what is on board. */
7799                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7800
7801                 /* Some material-based adjudications that have to be made before stalemate test */
7802                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7803                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7804                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7805                      if(canAdjudicate && appData.checkMates) {
7806                          if(engineOpponent)
7807                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7808                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7809                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7810                          return 1;
7811                      }
7812                 }
7813
7814                 /* Bare King in Shatranj (loses) or Losers (wins) */
7815                 if( nrW == 1 || nrB == 1) {
7816                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7817                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7818                      if(canAdjudicate && appData.checkMates) {
7819                          if(engineOpponent)
7820                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7821                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7822                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7823                          return 1;
7824                      }
7825                   } else
7826                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7827                   {    /* bare King */
7828                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7829                         if(canAdjudicate && appData.checkMates) {
7830                             /* but only adjudicate if adjudication enabled */
7831                             if(engineOpponent)
7832                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7833                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7834                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7835                             return 1;
7836                         }
7837                   }
7838                 } else bare = 1;
7839
7840
7841             // don't wait for engine to announce game end if we can judge ourselves
7842             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7843               case MT_CHECK:
7844                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7845                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7846                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7847                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7848                             checkCnt++;
7849                         if(checkCnt >= 2) {
7850                             reason = "Xboard adjudication: 3rd check";
7851                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7852                             break;
7853                         }
7854                     }
7855                 }
7856               case MT_NONE:
7857               default:
7858                 break;
7859               case MT_STALEMATE:
7860               case MT_STAINMATE:
7861                 reason = "Xboard adjudication: Stalemate";
7862                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7863                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7864                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7865                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7866                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7867                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7868                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7869                                                                         EP_CHECKMATE : EP_WINS);
7870                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7871                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7872                 }
7873                 break;
7874               case MT_CHECKMATE:
7875                 reason = "Xboard adjudication: Checkmate";
7876                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7877                 if(gameInfo.variant == VariantShogi) {
7878                     if(forwardMostMove > backwardMostMove
7879                        && moveList[forwardMostMove-1][1] == '@'
7880                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7881                         reason = "XBoard adjudication: pawn-drop mate";
7882                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7883                     }
7884                 }
7885                 break;
7886             }
7887
7888                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7889                     case EP_STALEMATE:
7890                         result = GameIsDrawn; break;
7891                     case EP_CHECKMATE:
7892                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7893                     case EP_WINS:
7894                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7895                     default:
7896                         result = EndOfFile;
7897                 }
7898                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7899                     if(engineOpponent)
7900                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7901                     GameEnds( result, reason, GE_XBOARD );
7902                     return 1;
7903                 }
7904
7905                 /* Next absolutely insufficient mating material. */
7906                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7907                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7908                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7909
7910                      /* always flag draws, for judging claims */
7911                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7912
7913                      if(canAdjudicate && appData.materialDraws) {
7914                          /* but only adjudicate them if adjudication enabled */
7915                          if(engineOpponent) {
7916                            SendToProgram("force\n", engineOpponent); // suppress reply
7917                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7918                          }
7919                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7920                          return 1;
7921                      }
7922                 }
7923
7924                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7925                 if(gameInfo.variant == VariantXiangqi ?
7926                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7927                  : nrW + nrB == 4 &&
7928                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7929                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7930                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7931                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7932                    ) ) {
7933                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7934                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7935                           if(engineOpponent) {
7936                             SendToProgram("force\n", engineOpponent); // suppress reply
7937                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7938                           }
7939                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7940                           return 1;
7941                      }
7942                 } else moveCount = 6;
7943             }
7944
7945         // Repetition draws and 50-move rule can be applied independently of legality testing
7946
7947                 /* Check for rep-draws */
7948                 count = 0;
7949                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7950                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7951                 for(k = forwardMostMove-2;
7952                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7953                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7954                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7955                     k-=2)
7956                 {   int rights=0;
7957                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7958                         /* compare castling rights */
7959                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7960                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7961                                 rights++; /* King lost rights, while rook still had them */
7962                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7963                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7964                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7965                                    rights++; /* but at least one rook lost them */
7966                         }
7967                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7968                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7969                                 rights++;
7970                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7971                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7972                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7973                                    rights++;
7974                         }
7975                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7976                             && appData.drawRepeats > 1) {
7977                              /* adjudicate after user-specified nr of repeats */
7978                              int result = GameIsDrawn;
7979                              char *details = "XBoard adjudication: repetition draw";
7980                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7981                                 // [HGM] xiangqi: check for forbidden perpetuals
7982                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7983                                 for(m=forwardMostMove; m>k; m-=2) {
7984                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7985                                         ourPerpetual = 0; // the current mover did not always check
7986                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7987                                         hisPerpetual = 0; // the opponent did not always check
7988                                 }
7989                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7990                                                                         ourPerpetual, hisPerpetual);
7991                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7992                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7993                                     details = "Xboard adjudication: perpetual checking";
7994                                 } else
7995                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7996                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7997                                 } else
7998                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7999                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8000                                         result = BlackWins;
8001                                         details = "Xboard adjudication: repetition";
8002                                     }
8003                                 } else // it must be XQ
8004                                 // Now check for perpetual chases
8005                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8006                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8007                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8008                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8009                                         static char resdet[MSG_SIZ];
8010                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8011                                         details = resdet;
8012                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8013                                     } else
8014                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8015                                         break; // Abort repetition-checking loop.
8016                                 }
8017                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8018                              }
8019                              if(engineOpponent) {
8020                                SendToProgram("force\n", engineOpponent); // suppress reply
8021                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8022                              }
8023                              GameEnds( result, details, GE_XBOARD );
8024                              return 1;
8025                         }
8026                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8027                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8028                     }
8029                 }
8030
8031                 /* Now we test for 50-move draws. Determine ply count */
8032                 count = forwardMostMove;
8033                 /* look for last irreversble move */
8034                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8035                     count--;
8036                 /* if we hit starting position, add initial plies */
8037                 if( count == backwardMostMove )
8038                     count -= initialRulePlies;
8039                 count = forwardMostMove - count;
8040                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8041                         // adjust reversible move counter for checks in Xiangqi
8042                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8043                         if(i < backwardMostMove) i = backwardMostMove;
8044                         while(i <= forwardMostMove) {
8045                                 lastCheck = inCheck; // check evasion does not count
8046                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8047                                 if(inCheck || lastCheck) count--; // check does not count
8048                                 i++;
8049                         }
8050                 }
8051                 if( count >= 100)
8052                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8053                          /* this is used to judge if draw claims are legal */
8054                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8055                          if(engineOpponent) {
8056                            SendToProgram("force\n", engineOpponent); // suppress reply
8057                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8058                          }
8059                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8060                          return 1;
8061                 }
8062
8063                 /* if draw offer is pending, treat it as a draw claim
8064                  * when draw condition present, to allow engines a way to
8065                  * claim draws before making their move to avoid a race
8066                  * condition occurring after their move
8067                  */
8068                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8069                          char *p = NULL;
8070                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8071                              p = "Draw claim: 50-move rule";
8072                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8073                              p = "Draw claim: 3-fold repetition";
8074                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8075                              p = "Draw claim: insufficient mating material";
8076                          if( p != NULL && canAdjudicate) {
8077                              if(engineOpponent) {
8078                                SendToProgram("force\n", engineOpponent); // suppress reply
8079                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8080                              }
8081                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8082                              return 1;
8083                          }
8084                 }
8085
8086                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8087                     if(engineOpponent) {
8088                       SendToProgram("force\n", engineOpponent); // suppress reply
8089                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8090                     }
8091                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8092                     return 1;
8093                 }
8094         return 0;
8095 }
8096
8097 char *
8098 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8099 {   // [HGM] book: this routine intercepts moves to simulate book replies
8100     char *bookHit = NULL;
8101
8102     //first determine if the incoming move brings opponent into his book
8103     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8104         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8105     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8106     if(bookHit != NULL && !cps->bookSuspend) {
8107         // make sure opponent is not going to reply after receiving move to book position
8108         SendToProgram("force\n", cps);
8109         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8110     }
8111     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8112     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8113     // now arrange restart after book miss
8114     if(bookHit) {
8115         // after a book hit we never send 'go', and the code after the call to this routine
8116         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8117         char buf[MSG_SIZ], *move = bookHit;
8118         if(cps->useSAN) {
8119             int fromX, fromY, toX, toY;
8120             char promoChar;
8121             ChessMove moveType;
8122             move = buf + 30;
8123             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8124                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8125                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8126                                     PosFlags(forwardMostMove),
8127                                     fromY, fromX, toY, toX, promoChar, move);
8128             } else {
8129                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8130                 bookHit = NULL;
8131             }
8132         }
8133         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8134         SendToProgram(buf, cps);
8135         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8136     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8137         SendToProgram("go\n", cps);
8138         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8139     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8140         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8141             SendToProgram("go\n", cps);
8142         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8143     }
8144     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8145 }
8146
8147 int
8148 LoadError (char *errmess, ChessProgramState *cps)
8149 {   // unloads engine and switches back to -ncp mode if it was first
8150     if(cps->initDone) return FALSE;
8151     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8152     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8153     cps->pr = NoProc;
8154     if(cps == &first) {
8155         appData.noChessProgram = TRUE;
8156         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8157         gameMode = BeginningOfGame; ModeHighlight();
8158         SetNCPMode();
8159     }
8160     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8161     DisplayMessage("", ""); // erase waiting message
8162     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8163     return TRUE;
8164 }
8165
8166 char *savedMessage;
8167 ChessProgramState *savedState;
8168 void
8169 DeferredBookMove (void)
8170 {
8171         if(savedState->lastPing != savedState->lastPong)
8172                     ScheduleDelayedEvent(DeferredBookMove, 10);
8173         else
8174         HandleMachineMove(savedMessage, savedState);
8175 }
8176
8177 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8178 static ChessProgramState *stalledEngine;
8179 static char stashedInputMove[MSG_SIZ];
8180
8181 void
8182 HandleMachineMove (char *message, ChessProgramState *cps)
8183 {
8184     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8185     char realname[MSG_SIZ];
8186     int fromX, fromY, toX, toY;
8187     ChessMove moveType;
8188     char promoChar;
8189     char *p, *pv=buf1;
8190     int machineWhite, oldError;
8191     char *bookHit;
8192
8193     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8194         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8195         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8196             DisplayError(_("Invalid pairing from pairing engine"), 0);
8197             return;
8198         }
8199         pairingReceived = 1;
8200         NextMatchGame();
8201         return; // Skim the pairing messages here.
8202     }
8203
8204     oldError = cps->userError; cps->userError = 0;
8205
8206 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8207     /*
8208      * Kludge to ignore BEL characters
8209      */
8210     while (*message == '\007') message++;
8211
8212     /*
8213      * [HGM] engine debug message: ignore lines starting with '#' character
8214      */
8215     if(cps->debug && *message == '#') return;
8216
8217     /*
8218      * Look for book output
8219      */
8220     if (cps == &first && bookRequested) {
8221         if (message[0] == '\t' || message[0] == ' ') {
8222             /* Part of the book output is here; append it */
8223             strcat(bookOutput, message);
8224             strcat(bookOutput, "  \n");
8225             return;
8226         } else if (bookOutput[0] != NULLCHAR) {
8227             /* All of book output has arrived; display it */
8228             char *p = bookOutput;
8229             while (*p != NULLCHAR) {
8230                 if (*p == '\t') *p = ' ';
8231                 p++;
8232             }
8233             DisplayInformation(bookOutput);
8234             bookRequested = FALSE;
8235             /* Fall through to parse the current output */
8236         }
8237     }
8238
8239     /*
8240      * Look for machine move.
8241      */
8242     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8243         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8244     {
8245         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8246             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8247             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8248             stalledEngine = cps;
8249             if(appData.ponderNextMove) { // bring opponent out of ponder
8250                 if(gameMode == TwoMachinesPlay) {
8251                     if(cps->other->pause)
8252                         PauseEngine(cps->other);
8253                     else
8254                         SendToProgram("easy\n", cps->other);
8255                 }
8256             }
8257             StopClocks();
8258             return;
8259         }
8260
8261         /* This method is only useful on engines that support ping */
8262         if (cps->lastPing != cps->lastPong) {
8263           if (gameMode == BeginningOfGame) {
8264             /* Extra move from before last new; ignore */
8265             if (appData.debugMode) {
8266                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8267             }
8268           } else {
8269             if (appData.debugMode) {
8270                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8271                         cps->which, gameMode);
8272             }
8273
8274             SendToProgram("undo\n", cps);
8275           }
8276           return;
8277         }
8278
8279         switch (gameMode) {
8280           case BeginningOfGame:
8281             /* Extra move from before last reset; ignore */
8282             if (appData.debugMode) {
8283                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8284             }
8285             return;
8286
8287           case EndOfGame:
8288           case IcsIdle:
8289           default:
8290             /* Extra move after we tried to stop.  The mode test is
8291                not a reliable way of detecting this problem, but it's
8292                the best we can do on engines that don't support ping.
8293             */
8294             if (appData.debugMode) {
8295                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8296                         cps->which, gameMode);
8297             }
8298             SendToProgram("undo\n", cps);
8299             return;
8300
8301           case MachinePlaysWhite:
8302           case IcsPlayingWhite:
8303             machineWhite = TRUE;
8304             break;
8305
8306           case MachinePlaysBlack:
8307           case IcsPlayingBlack:
8308             machineWhite = FALSE;
8309             break;
8310
8311           case TwoMachinesPlay:
8312             machineWhite = (cps->twoMachinesColor[0] == 'w');
8313             break;
8314         }
8315         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8316             if (appData.debugMode) {
8317                 fprintf(debugFP,
8318                         "Ignoring move out of turn by %s, gameMode %d"
8319                         ", forwardMost %d\n",
8320                         cps->which, gameMode, forwardMostMove);
8321             }
8322             return;
8323         }
8324
8325         if(cps->alphaRank) AlphaRank(machineMove, 4);
8326         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8327                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8328             /* Machine move could not be parsed; ignore it. */
8329           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8330                     machineMove, _(cps->which));
8331             DisplayMoveError(buf1);
8332             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8333                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8334             if (gameMode == TwoMachinesPlay) {
8335               GameEnds(machineWhite ? BlackWins : WhiteWins,
8336                        buf1, GE_XBOARD);
8337             }
8338             return;
8339         }
8340
8341         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8342         /* So we have to redo legality test with true e.p. status here,  */
8343         /* to make sure an illegal e.p. capture does not slip through,   */
8344         /* to cause a forfeit on a justified illegal-move complaint      */
8345         /* of the opponent.                                              */
8346         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8347            ChessMove moveType;
8348            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8349                              fromY, fromX, toY, toX, promoChar);
8350             if(moveType == IllegalMove) {
8351               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8352                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8353                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8354                            buf1, GE_XBOARD);
8355                 return;
8356            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8357            /* [HGM] Kludge to handle engines that send FRC-style castling
8358               when they shouldn't (like TSCP-Gothic) */
8359            switch(moveType) {
8360              case WhiteASideCastleFR:
8361              case BlackASideCastleFR:
8362                toX+=2;
8363                currentMoveString[2]++;
8364                break;
8365              case WhiteHSideCastleFR:
8366              case BlackHSideCastleFR:
8367                toX--;
8368                currentMoveString[2]--;
8369                break;
8370              default: ; // nothing to do, but suppresses warning of pedantic compilers
8371            }
8372         }
8373         hintRequested = FALSE;
8374         lastHint[0] = NULLCHAR;
8375         bookRequested = FALSE;
8376         /* Program may be pondering now */
8377         cps->maybeThinking = TRUE;
8378         if (cps->sendTime == 2) cps->sendTime = 1;
8379         if (cps->offeredDraw) cps->offeredDraw--;
8380
8381         /* [AS] Save move info*/
8382         pvInfoList[ forwardMostMove ].score = programStats.score;
8383         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8384         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8385
8386         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8387
8388         /* Test suites abort the 'game' after one move */
8389         if(*appData.finger) {
8390            static FILE *f;
8391            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8392            if(!f) f = fopen(appData.finger, "w");
8393            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8394            else { DisplayFatalError("Bad output file", errno, 0); return; }
8395            free(fen);
8396            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8397         }
8398
8399         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8400         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8401             int count = 0;
8402
8403             while( count < adjudicateLossPlies ) {
8404                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8405
8406                 if( count & 1 ) {
8407                     score = -score; /* Flip score for winning side */
8408                 }
8409
8410                 if( score > adjudicateLossThreshold ) {
8411                     break;
8412                 }
8413
8414                 count++;
8415             }
8416
8417             if( count >= adjudicateLossPlies ) {
8418                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8419
8420                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8421                     "Xboard adjudication",
8422                     GE_XBOARD );
8423
8424                 return;
8425             }
8426         }
8427
8428         if(Adjudicate(cps)) {
8429             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8430             return; // [HGM] adjudicate: for all automatic game ends
8431         }
8432
8433 #if ZIPPY
8434         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8435             first.initDone) {
8436           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8437                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8438                 SendToICS("draw ");
8439                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8440           }
8441           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8442           ics_user_moved = 1;
8443           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8444                 char buf[3*MSG_SIZ];
8445
8446                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8447                         programStats.score / 100.,
8448                         programStats.depth,
8449                         programStats.time / 100.,
8450                         (unsigned int)programStats.nodes,
8451                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8452                         programStats.movelist);
8453                 SendToICS(buf);
8454 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8455           }
8456         }
8457 #endif
8458
8459         /* [AS] Clear stats for next move */
8460         ClearProgramStats();
8461         thinkOutput[0] = NULLCHAR;
8462         hiddenThinkOutputState = 0;
8463
8464         bookHit = NULL;
8465         if (gameMode == TwoMachinesPlay) {
8466             /* [HGM] relaying draw offers moved to after reception of move */
8467             /* and interpreting offer as claim if it brings draw condition */
8468             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8469                 SendToProgram("draw\n", cps->other);
8470             }
8471             if (cps->other->sendTime) {
8472                 SendTimeRemaining(cps->other,
8473                                   cps->other->twoMachinesColor[0] == 'w');
8474             }
8475             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8476             if (firstMove && !bookHit) {
8477                 firstMove = FALSE;
8478                 if (cps->other->useColors) {
8479                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8480                 }
8481                 SendToProgram("go\n", cps->other);
8482             }
8483             cps->other->maybeThinking = TRUE;
8484         }
8485
8486         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8487
8488         if (!pausing && appData.ringBellAfterMoves) {
8489             RingBell();
8490         }
8491
8492         /*
8493          * Reenable menu items that were disabled while
8494          * machine was thinking
8495          */
8496         if (gameMode != TwoMachinesPlay)
8497             SetUserThinkingEnables();
8498
8499         // [HGM] book: after book hit opponent has received move and is now in force mode
8500         // force the book reply into it, and then fake that it outputted this move by jumping
8501         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8502         if(bookHit) {
8503                 static char bookMove[MSG_SIZ]; // a bit generous?
8504
8505                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8506                 strcat(bookMove, bookHit);
8507                 message = bookMove;
8508                 cps = cps->other;
8509                 programStats.nodes = programStats.depth = programStats.time =
8510                 programStats.score = programStats.got_only_move = 0;
8511                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8512
8513                 if(cps->lastPing != cps->lastPong) {
8514                     savedMessage = message; // args for deferred call
8515                     savedState = cps;
8516                     ScheduleDelayedEvent(DeferredBookMove, 10);
8517                     return;
8518                 }
8519                 goto FakeBookMove;
8520         }
8521
8522         return;
8523     }
8524
8525     /* Set special modes for chess engines.  Later something general
8526      *  could be added here; for now there is just one kludge feature,
8527      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8528      *  when "xboard" is given as an interactive command.
8529      */
8530     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8531         cps->useSigint = FALSE;
8532         cps->useSigterm = FALSE;
8533     }
8534     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8535       ParseFeatures(message+8, cps);
8536       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8537     }
8538
8539     if (!strncmp(message, "setup ", 6) && 
8540         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8541                                         ) { // [HGM] allow first engine to define opening position
8542       int dummy, s=6; char buf[MSG_SIZ];
8543       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8544       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8545       if(startedFromSetupPosition) return;
8546       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8547       ParseFEN(boards[0], &dummy, message+s);
8548       DrawPosition(TRUE, boards[0]);
8549       startedFromSetupPosition = TRUE;
8550       return;
8551     }
8552     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8553      * want this, I was asked to put it in, and obliged.
8554      */
8555     if (!strncmp(message, "setboard ", 9)) {
8556         Board initial_position;
8557
8558         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8559
8560         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8561             DisplayError(_("Bad FEN received from engine"), 0);
8562             return ;
8563         } else {
8564            Reset(TRUE, FALSE);
8565            CopyBoard(boards[0], initial_position);
8566            initialRulePlies = FENrulePlies;
8567            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8568            else gameMode = MachinePlaysBlack;
8569            DrawPosition(FALSE, boards[currentMove]);
8570         }
8571         return;
8572     }
8573
8574     /*
8575      * Look for communication commands
8576      */
8577     if (!strncmp(message, "telluser ", 9)) {
8578         if(message[9] == '\\' && message[10] == '\\')
8579             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8580         PlayTellSound();
8581         DisplayNote(message + 9);
8582         return;
8583     }
8584     if (!strncmp(message, "tellusererror ", 14)) {
8585         cps->userError = 1;
8586         if(message[14] == '\\' && message[15] == '\\')
8587             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8588         PlayTellSound();
8589         DisplayError(message + 14, 0);
8590         return;
8591     }
8592     if (!strncmp(message, "tellopponent ", 13)) {
8593       if (appData.icsActive) {
8594         if (loggedOn) {
8595           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8596           SendToICS(buf1);
8597         }
8598       } else {
8599         DisplayNote(message + 13);
8600       }
8601       return;
8602     }
8603     if (!strncmp(message, "tellothers ", 11)) {
8604       if (appData.icsActive) {
8605         if (loggedOn) {
8606           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8607           SendToICS(buf1);
8608         }
8609       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8610       return;
8611     }
8612     if (!strncmp(message, "tellall ", 8)) {
8613       if (appData.icsActive) {
8614         if (loggedOn) {
8615           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8616           SendToICS(buf1);
8617         }
8618       } else {
8619         DisplayNote(message + 8);
8620       }
8621       return;
8622     }
8623     if (strncmp(message, "warning", 7) == 0) {
8624         /* Undocumented feature, use tellusererror in new code */
8625         DisplayError(message, 0);
8626         return;
8627     }
8628     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8629         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8630         strcat(realname, " query");
8631         AskQuestion(realname, buf2, buf1, cps->pr);
8632         return;
8633     }
8634     /* Commands from the engine directly to ICS.  We don't allow these to be
8635      *  sent until we are logged on. Crafty kibitzes have been known to
8636      *  interfere with the login process.
8637      */
8638     if (loggedOn) {
8639         if (!strncmp(message, "tellics ", 8)) {
8640             SendToICS(message + 8);
8641             SendToICS("\n");
8642             return;
8643         }
8644         if (!strncmp(message, "tellicsnoalias ", 15)) {
8645             SendToICS(ics_prefix);
8646             SendToICS(message + 15);
8647             SendToICS("\n");
8648             return;
8649         }
8650         /* The following are for backward compatibility only */
8651         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8652             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8653             SendToICS(ics_prefix);
8654             SendToICS(message);
8655             SendToICS("\n");
8656             return;
8657         }
8658     }
8659     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8660         return;
8661     }
8662     if(!strncmp(message, "highlight ", 10)) {
8663         if(appData.testLegality && appData.markers) return;
8664         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8665         return;
8666     }
8667     /*
8668      * If the move is illegal, cancel it and redraw the board.
8669      * Also deal with other error cases.  Matching is rather loose
8670      * here to accommodate engines written before the spec.
8671      */
8672     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8673         strncmp(message, "Error", 5) == 0) {
8674         if (StrStr(message, "name") ||
8675             StrStr(message, "rating") || StrStr(message, "?") ||
8676             StrStr(message, "result") || StrStr(message, "board") ||
8677             StrStr(message, "bk") || StrStr(message, "computer") ||
8678             StrStr(message, "variant") || StrStr(message, "hint") ||
8679             StrStr(message, "random") || StrStr(message, "depth") ||
8680             StrStr(message, "accepted")) {
8681             return;
8682         }
8683         if (StrStr(message, "protover")) {
8684           /* Program is responding to input, so it's apparently done
8685              initializing, and this error message indicates it is
8686              protocol version 1.  So we don't need to wait any longer
8687              for it to initialize and send feature commands. */
8688           FeatureDone(cps, 1);
8689           cps->protocolVersion = 1;
8690           return;
8691         }
8692         cps->maybeThinking = FALSE;
8693
8694         if (StrStr(message, "draw")) {
8695             /* Program doesn't have "draw" command */
8696             cps->sendDrawOffers = 0;
8697             return;
8698         }
8699         if (cps->sendTime != 1 &&
8700             (StrStr(message, "time") || StrStr(message, "otim"))) {
8701           /* Program apparently doesn't have "time" or "otim" command */
8702           cps->sendTime = 0;
8703           return;
8704         }
8705         if (StrStr(message, "analyze")) {
8706             cps->analysisSupport = FALSE;
8707             cps->analyzing = FALSE;
8708 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8709             EditGameEvent(); // [HGM] try to preserve loaded game
8710             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8711             DisplayError(buf2, 0);
8712             return;
8713         }
8714         if (StrStr(message, "(no matching move)st")) {
8715           /* Special kludge for GNU Chess 4 only */
8716           cps->stKludge = TRUE;
8717           SendTimeControl(cps, movesPerSession, timeControl,
8718                           timeIncrement, appData.searchDepth,
8719                           searchTime);
8720           return;
8721         }
8722         if (StrStr(message, "(no matching move)sd")) {
8723           /* Special kludge for GNU Chess 4 only */
8724           cps->sdKludge = TRUE;
8725           SendTimeControl(cps, movesPerSession, timeControl,
8726                           timeIncrement, appData.searchDepth,
8727                           searchTime);
8728           return;
8729         }
8730         if (!StrStr(message, "llegal")) {
8731             return;
8732         }
8733         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8734             gameMode == IcsIdle) return;
8735         if (forwardMostMove <= backwardMostMove) return;
8736         if (pausing) PauseEvent();
8737       if(appData.forceIllegal) {
8738             // [HGM] illegal: machine refused move; force position after move into it
8739           SendToProgram("force\n", cps);
8740           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8741                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8742                 // when black is to move, while there might be nothing on a2 or black
8743                 // might already have the move. So send the board as if white has the move.
8744                 // But first we must change the stm of the engine, as it refused the last move
8745                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8746                 if(WhiteOnMove(forwardMostMove)) {
8747                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8748                     SendBoard(cps, forwardMostMove); // kludgeless board
8749                 } else {
8750                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8751                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8752                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8753                 }
8754           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8755             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8756                  gameMode == TwoMachinesPlay)
8757               SendToProgram("go\n", cps);
8758             return;
8759       } else
8760         if (gameMode == PlayFromGameFile) {
8761             /* Stop reading this game file */
8762             gameMode = EditGame;
8763             ModeHighlight();
8764         }
8765         /* [HGM] illegal-move claim should forfeit game when Xboard */
8766         /* only passes fully legal moves                            */
8767         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8768             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8769                                 "False illegal-move claim", GE_XBOARD );
8770             return; // do not take back move we tested as valid
8771         }
8772         currentMove = forwardMostMove-1;
8773         DisplayMove(currentMove-1); /* before DisplayMoveError */
8774         SwitchClocks(forwardMostMove-1); // [HGM] race
8775         DisplayBothClocks();
8776         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8777                 parseList[currentMove], _(cps->which));
8778         DisplayMoveError(buf1);
8779         DrawPosition(FALSE, boards[currentMove]);
8780
8781         SetUserThinkingEnables();
8782         return;
8783     }
8784     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8785         /* Program has a broken "time" command that
8786            outputs a string not ending in newline.
8787            Don't use it. */
8788         cps->sendTime = 0;
8789     }
8790
8791     /*
8792      * If chess program startup fails, exit with an error message.
8793      * Attempts to recover here are futile. [HGM] Well, we try anyway
8794      */
8795     if ((StrStr(message, "unknown host") != NULL)
8796         || (StrStr(message, "No remote directory") != NULL)
8797         || (StrStr(message, "not found") != NULL)
8798         || (StrStr(message, "No such file") != NULL)
8799         || (StrStr(message, "can't alloc") != NULL)
8800         || (StrStr(message, "Permission denied") != NULL)) {
8801
8802         cps->maybeThinking = FALSE;
8803         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8804                 _(cps->which), cps->program, cps->host, message);
8805         RemoveInputSource(cps->isr);
8806         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8807             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8808             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8809         }
8810         return;
8811     }
8812
8813     /*
8814      * Look for hint output
8815      */
8816     if (sscanf(message, "Hint: %s", buf1) == 1) {
8817         if (cps == &first && hintRequested) {
8818             hintRequested = FALSE;
8819             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8820                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8821                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8822                                     PosFlags(forwardMostMove),
8823                                     fromY, fromX, toY, toX, promoChar, buf1);
8824                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8825                 DisplayInformation(buf2);
8826             } else {
8827                 /* Hint move could not be parsed!? */
8828               snprintf(buf2, sizeof(buf2),
8829                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8830                         buf1, _(cps->which));
8831                 DisplayError(buf2, 0);
8832             }
8833         } else {
8834           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8835         }
8836         return;
8837     }
8838
8839     /*
8840      * Ignore other messages if game is not in progress
8841      */
8842     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8843         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8844
8845     /*
8846      * look for win, lose, draw, or draw offer
8847      */
8848     if (strncmp(message, "1-0", 3) == 0) {
8849         char *p, *q, *r = "";
8850         p = strchr(message, '{');
8851         if (p) {
8852             q = strchr(p, '}');
8853             if (q) {
8854                 *q = NULLCHAR;
8855                 r = p + 1;
8856             }
8857         }
8858         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8859         return;
8860     } else if (strncmp(message, "0-1", 3) == 0) {
8861         char *p, *q, *r = "";
8862         p = strchr(message, '{');
8863         if (p) {
8864             q = strchr(p, '}');
8865             if (q) {
8866                 *q = NULLCHAR;
8867                 r = p + 1;
8868             }
8869         }
8870         /* Kludge for Arasan 4.1 bug */
8871         if (strcmp(r, "Black resigns") == 0) {
8872             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8873             return;
8874         }
8875         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8876         return;
8877     } else if (strncmp(message, "1/2", 3) == 0) {
8878         char *p, *q, *r = "";
8879         p = strchr(message, '{');
8880         if (p) {
8881             q = strchr(p, '}');
8882             if (q) {
8883                 *q = NULLCHAR;
8884                 r = p + 1;
8885             }
8886         }
8887
8888         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8889         return;
8890
8891     } else if (strncmp(message, "White resign", 12) == 0) {
8892         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8893         return;
8894     } else if (strncmp(message, "Black resign", 12) == 0) {
8895         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8896         return;
8897     } else if (strncmp(message, "White matches", 13) == 0 ||
8898                strncmp(message, "Black matches", 13) == 0   ) {
8899         /* [HGM] ignore GNUShogi noises */
8900         return;
8901     } else if (strncmp(message, "White", 5) == 0 &&
8902                message[5] != '(' &&
8903                StrStr(message, "Black") == NULL) {
8904         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8905         return;
8906     } else if (strncmp(message, "Black", 5) == 0 &&
8907                message[5] != '(') {
8908         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8909         return;
8910     } else if (strcmp(message, "resign") == 0 ||
8911                strcmp(message, "computer resigns") == 0) {
8912         switch (gameMode) {
8913           case MachinePlaysBlack:
8914           case IcsPlayingBlack:
8915             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8916             break;
8917           case MachinePlaysWhite:
8918           case IcsPlayingWhite:
8919             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8920             break;
8921           case TwoMachinesPlay:
8922             if (cps->twoMachinesColor[0] == 'w')
8923               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8924             else
8925               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8926             break;
8927           default:
8928             /* can't happen */
8929             break;
8930         }
8931         return;
8932     } else if (strncmp(message, "opponent mates", 14) == 0) {
8933         switch (gameMode) {
8934           case MachinePlaysBlack:
8935           case IcsPlayingBlack:
8936             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8937             break;
8938           case MachinePlaysWhite:
8939           case IcsPlayingWhite:
8940             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8941             break;
8942           case TwoMachinesPlay:
8943             if (cps->twoMachinesColor[0] == 'w')
8944               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8945             else
8946               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8947             break;
8948           default:
8949             /* can't happen */
8950             break;
8951         }
8952         return;
8953     } else if (strncmp(message, "computer mates", 14) == 0) {
8954         switch (gameMode) {
8955           case MachinePlaysBlack:
8956           case IcsPlayingBlack:
8957             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8958             break;
8959           case MachinePlaysWhite:
8960           case IcsPlayingWhite:
8961             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8962             break;
8963           case TwoMachinesPlay:
8964             if (cps->twoMachinesColor[0] == 'w')
8965               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8966             else
8967               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8968             break;
8969           default:
8970             /* can't happen */
8971             break;
8972         }
8973         return;
8974     } else if (strncmp(message, "checkmate", 9) == 0) {
8975         if (WhiteOnMove(forwardMostMove)) {
8976             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8977         } else {
8978             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8979         }
8980         return;
8981     } else if (strstr(message, "Draw") != NULL ||
8982                strstr(message, "game is a draw") != NULL) {
8983         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8984         return;
8985     } else if (strstr(message, "offer") != NULL &&
8986                strstr(message, "draw") != NULL) {
8987 #if ZIPPY
8988         if (appData.zippyPlay && first.initDone) {
8989             /* Relay offer to ICS */
8990             SendToICS(ics_prefix);
8991             SendToICS("draw\n");
8992         }
8993 #endif
8994         cps->offeredDraw = 2; /* valid until this engine moves twice */
8995         if (gameMode == TwoMachinesPlay) {
8996             if (cps->other->offeredDraw) {
8997                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8998             /* [HGM] in two-machine mode we delay relaying draw offer      */
8999             /* until after we also have move, to see if it is really claim */
9000             }
9001         } else if (gameMode == MachinePlaysWhite ||
9002                    gameMode == MachinePlaysBlack) {
9003           if (userOfferedDraw) {
9004             DisplayInformation(_("Machine accepts your draw offer"));
9005             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9006           } else {
9007             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
9008           }
9009         }
9010     }
9011
9012
9013     /*
9014      * Look for thinking output
9015      */
9016     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9017           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9018                                 ) {
9019         int plylev, mvleft, mvtot, curscore, time;
9020         char mvname[MOVE_LEN];
9021         u64 nodes; // [DM]
9022         char plyext;
9023         int ignore = FALSE;
9024         int prefixHint = FALSE;
9025         mvname[0] = NULLCHAR;
9026
9027         switch (gameMode) {
9028           case MachinePlaysBlack:
9029           case IcsPlayingBlack:
9030             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9031             break;
9032           case MachinePlaysWhite:
9033           case IcsPlayingWhite:
9034             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9035             break;
9036           case AnalyzeMode:
9037           case AnalyzeFile:
9038             break;
9039           case IcsObserving: /* [DM] icsEngineAnalyze */
9040             if (!appData.icsEngineAnalyze) ignore = TRUE;
9041             break;
9042           case TwoMachinesPlay:
9043             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9044                 ignore = TRUE;
9045             }
9046             break;
9047           default:
9048             ignore = TRUE;
9049             break;
9050         }
9051
9052         if (!ignore) {
9053             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9054             buf1[0] = NULLCHAR;
9055             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9056                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9057
9058                 if (plyext != ' ' && plyext != '\t') {
9059                     time *= 100;
9060                 }
9061
9062                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9063                 if( cps->scoreIsAbsolute &&
9064                     ( gameMode == MachinePlaysBlack ||
9065                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9066                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9067                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9068                      !WhiteOnMove(currentMove)
9069                     ) )
9070                 {
9071                     curscore = -curscore;
9072                 }
9073
9074                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9075
9076                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9077                         char buf[MSG_SIZ];
9078                         FILE *f;
9079                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9080                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9081                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9082                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9083                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9084                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9085                                 fclose(f);
9086                         } else DisplayError(_("failed writing PV"), 0);
9087                 }
9088
9089                 tempStats.depth = plylev;
9090                 tempStats.nodes = nodes;
9091                 tempStats.time = time;
9092                 tempStats.score = curscore;
9093                 tempStats.got_only_move = 0;
9094
9095                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9096                         int ticklen;
9097
9098                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9099                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9100                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9101                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9102                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9103                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9104                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9105                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9106                 }
9107
9108                 /* Buffer overflow protection */
9109                 if (pv[0] != NULLCHAR) {
9110                     if (strlen(pv) >= sizeof(tempStats.movelist)
9111                         && appData.debugMode) {
9112                         fprintf(debugFP,
9113                                 "PV is too long; using the first %u bytes.\n",
9114                                 (unsigned) sizeof(tempStats.movelist) - 1);
9115                     }
9116
9117                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9118                 } else {
9119                     sprintf(tempStats.movelist, " no PV\n");
9120                 }
9121
9122                 if (tempStats.seen_stat) {
9123                     tempStats.ok_to_send = 1;
9124                 }
9125
9126                 if (strchr(tempStats.movelist, '(') != NULL) {
9127                     tempStats.line_is_book = 1;
9128                     tempStats.nr_moves = 0;
9129                     tempStats.moves_left = 0;
9130                 } else {
9131                     tempStats.line_is_book = 0;
9132                 }
9133
9134                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9135                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9136
9137                 SendProgramStatsToFrontend( cps, &tempStats );
9138
9139                 /*
9140                     [AS] Protect the thinkOutput buffer from overflow... this
9141                     is only useful if buf1 hasn't overflowed first!
9142                 */
9143                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9144                          plylev,
9145                          (gameMode == TwoMachinesPlay ?
9146                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9147                          ((double) curscore) / 100.0,
9148                          prefixHint ? lastHint : "",
9149                          prefixHint ? " " : "" );
9150
9151                 if( buf1[0] != NULLCHAR ) {
9152                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9153
9154                     if( strlen(pv) > max_len ) {
9155                         if( appData.debugMode) {
9156                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9157                         }
9158                         pv[max_len+1] = '\0';
9159                     }
9160
9161                     strcat( thinkOutput, pv);
9162                 }
9163
9164                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9165                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9166                     DisplayMove(currentMove - 1);
9167                 }
9168                 return;
9169
9170             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9171                 /* crafty (9.25+) says "(only move) <move>"
9172                  * if there is only 1 legal move
9173                  */
9174                 sscanf(p, "(only move) %s", buf1);
9175                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9176                 sprintf(programStats.movelist, "%s (only move)", buf1);
9177                 programStats.depth = 1;
9178                 programStats.nr_moves = 1;
9179                 programStats.moves_left = 1;
9180                 programStats.nodes = 1;
9181                 programStats.time = 1;
9182                 programStats.got_only_move = 1;
9183
9184                 /* Not really, but we also use this member to
9185                    mean "line isn't going to change" (Crafty
9186                    isn't searching, so stats won't change) */
9187                 programStats.line_is_book = 1;
9188
9189                 SendProgramStatsToFrontend( cps, &programStats );
9190
9191                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9192                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9193                     DisplayMove(currentMove - 1);
9194                 }
9195                 return;
9196             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9197                               &time, &nodes, &plylev, &mvleft,
9198                               &mvtot, mvname) >= 5) {
9199                 /* The stat01: line is from Crafty (9.29+) in response
9200                    to the "." command */
9201                 programStats.seen_stat = 1;
9202                 cps->maybeThinking = TRUE;
9203
9204                 if (programStats.got_only_move || !appData.periodicUpdates)
9205                   return;
9206
9207                 programStats.depth = plylev;
9208                 programStats.time = time;
9209                 programStats.nodes = nodes;
9210                 programStats.moves_left = mvleft;
9211                 programStats.nr_moves = mvtot;
9212                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9213                 programStats.ok_to_send = 1;
9214                 programStats.movelist[0] = '\0';
9215
9216                 SendProgramStatsToFrontend( cps, &programStats );
9217
9218                 return;
9219
9220             } else if (strncmp(message,"++",2) == 0) {
9221                 /* Crafty 9.29+ outputs this */
9222                 programStats.got_fail = 2;
9223                 return;
9224
9225             } else if (strncmp(message,"--",2) == 0) {
9226                 /* Crafty 9.29+ outputs this */
9227                 programStats.got_fail = 1;
9228                 return;
9229
9230             } else if (thinkOutput[0] != NULLCHAR &&
9231                        strncmp(message, "    ", 4) == 0) {
9232                 unsigned message_len;
9233
9234                 p = message;
9235                 while (*p && *p == ' ') p++;
9236
9237                 message_len = strlen( p );
9238
9239                 /* [AS] Avoid buffer overflow */
9240                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9241                     strcat(thinkOutput, " ");
9242                     strcat(thinkOutput, p);
9243                 }
9244
9245                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9246                     strcat(programStats.movelist, " ");
9247                     strcat(programStats.movelist, p);
9248                 }
9249
9250                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9251                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9252                     DisplayMove(currentMove - 1);
9253                 }
9254                 return;
9255             }
9256         }
9257         else {
9258             buf1[0] = NULLCHAR;
9259
9260             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9261                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9262             {
9263                 ChessProgramStats cpstats;
9264
9265                 if (plyext != ' ' && plyext != '\t') {
9266                     time *= 100;
9267                 }
9268
9269                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9270                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9271                     curscore = -curscore;
9272                 }
9273
9274                 cpstats.depth = plylev;
9275                 cpstats.nodes = nodes;
9276                 cpstats.time = time;
9277                 cpstats.score = curscore;
9278                 cpstats.got_only_move = 0;
9279                 cpstats.movelist[0] = '\0';
9280
9281                 if (buf1[0] != NULLCHAR) {
9282                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9283                 }
9284
9285                 cpstats.ok_to_send = 0;
9286                 cpstats.line_is_book = 0;
9287                 cpstats.nr_moves = 0;
9288                 cpstats.moves_left = 0;
9289
9290                 SendProgramStatsToFrontend( cps, &cpstats );
9291             }
9292         }
9293     }
9294 }
9295
9296
9297 /* Parse a game score from the character string "game", and
9298    record it as the history of the current game.  The game
9299    score is NOT assumed to start from the standard position.
9300    The display is not updated in any way.
9301    */
9302 void
9303 ParseGameHistory (char *game)
9304 {
9305     ChessMove moveType;
9306     int fromX, fromY, toX, toY, boardIndex;
9307     char promoChar;
9308     char *p, *q;
9309     char buf[MSG_SIZ];
9310
9311     if (appData.debugMode)
9312       fprintf(debugFP, "Parsing game history: %s\n", game);
9313
9314     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9315     gameInfo.site = StrSave(appData.icsHost);
9316     gameInfo.date = PGNDate();
9317     gameInfo.round = StrSave("-");
9318
9319     /* Parse out names of players */
9320     while (*game == ' ') game++;
9321     p = buf;
9322     while (*game != ' ') *p++ = *game++;
9323     *p = NULLCHAR;
9324     gameInfo.white = StrSave(buf);
9325     while (*game == ' ') game++;
9326     p = buf;
9327     while (*game != ' ' && *game != '\n') *p++ = *game++;
9328     *p = NULLCHAR;
9329     gameInfo.black = StrSave(buf);
9330
9331     /* Parse moves */
9332     boardIndex = blackPlaysFirst ? 1 : 0;
9333     yynewstr(game);
9334     for (;;) {
9335         yyboardindex = boardIndex;
9336         moveType = (ChessMove) Myylex();
9337         switch (moveType) {
9338           case IllegalMove:             /* maybe suicide chess, etc. */
9339   if (appData.debugMode) {
9340     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9341     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9342     setbuf(debugFP, NULL);
9343   }
9344           case WhitePromotion:
9345           case BlackPromotion:
9346           case WhiteNonPromotion:
9347           case BlackNonPromotion:
9348           case NormalMove:
9349           case WhiteCapturesEnPassant:
9350           case BlackCapturesEnPassant:
9351           case WhiteKingSideCastle:
9352           case WhiteQueenSideCastle:
9353           case BlackKingSideCastle:
9354           case BlackQueenSideCastle:
9355           case WhiteKingSideCastleWild:
9356           case WhiteQueenSideCastleWild:
9357           case BlackKingSideCastleWild:
9358           case BlackQueenSideCastleWild:
9359           /* PUSH Fabien */
9360           case WhiteHSideCastleFR:
9361           case WhiteASideCastleFR:
9362           case BlackHSideCastleFR:
9363           case BlackASideCastleFR:
9364           /* POP Fabien */
9365             fromX = currentMoveString[0] - AAA;
9366             fromY = currentMoveString[1] - ONE;
9367             toX = currentMoveString[2] - AAA;
9368             toY = currentMoveString[3] - ONE;
9369             promoChar = currentMoveString[4];
9370             break;
9371           case WhiteDrop:
9372           case BlackDrop:
9373             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9374             fromX = moveType == WhiteDrop ?
9375               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9376             (int) CharToPiece(ToLower(currentMoveString[0]));
9377             fromY = DROP_RANK;
9378             toX = currentMoveString[2] - AAA;
9379             toY = currentMoveString[3] - ONE;
9380             promoChar = NULLCHAR;
9381             break;
9382           case AmbiguousMove:
9383             /* bug? */
9384             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9385   if (appData.debugMode) {
9386     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9387     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9388     setbuf(debugFP, NULL);
9389   }
9390             DisplayError(buf, 0);
9391             return;
9392           case ImpossibleMove:
9393             /* bug? */
9394             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9395   if (appData.debugMode) {
9396     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9397     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9398     setbuf(debugFP, NULL);
9399   }
9400             DisplayError(buf, 0);
9401             return;
9402           case EndOfFile:
9403             if (boardIndex < backwardMostMove) {
9404                 /* Oops, gap.  How did that happen? */
9405                 DisplayError(_("Gap in move list"), 0);
9406                 return;
9407             }
9408             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9409             if (boardIndex > forwardMostMove) {
9410                 forwardMostMove = boardIndex;
9411             }
9412             return;
9413           case ElapsedTime:
9414             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9415                 strcat(parseList[boardIndex-1], " ");
9416                 strcat(parseList[boardIndex-1], yy_text);
9417             }
9418             continue;
9419           case Comment:
9420           case PGNTag:
9421           case NAG:
9422           default:
9423             /* ignore */
9424             continue;
9425           case WhiteWins:
9426           case BlackWins:
9427           case GameIsDrawn:
9428           case GameUnfinished:
9429             if (gameMode == IcsExamining) {
9430                 if (boardIndex < backwardMostMove) {
9431                     /* Oops, gap.  How did that happen? */
9432                     return;
9433                 }
9434                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9435                 return;
9436             }
9437             gameInfo.result = moveType;
9438             p = strchr(yy_text, '{');
9439             if (p == NULL) p = strchr(yy_text, '(');
9440             if (p == NULL) {
9441                 p = yy_text;
9442                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9443             } else {
9444                 q = strchr(p, *p == '{' ? '}' : ')');
9445                 if (q != NULL) *q = NULLCHAR;
9446                 p++;
9447             }
9448             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9449             gameInfo.resultDetails = StrSave(p);
9450             continue;
9451         }
9452         if (boardIndex >= forwardMostMove &&
9453             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9454             backwardMostMove = blackPlaysFirst ? 1 : 0;
9455             return;
9456         }
9457         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9458                                  fromY, fromX, toY, toX, promoChar,
9459                                  parseList[boardIndex]);
9460         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9461         /* currentMoveString is set as a side-effect of yylex */
9462         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9463         strcat(moveList[boardIndex], "\n");
9464         boardIndex++;
9465         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9466         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9467           case MT_NONE:
9468           case MT_STALEMATE:
9469           default:
9470             break;
9471           case MT_CHECK:
9472             if(gameInfo.variant != VariantShogi)
9473                 strcat(parseList[boardIndex - 1], "+");
9474             break;
9475           case MT_CHECKMATE:
9476           case MT_STAINMATE:
9477             strcat(parseList[boardIndex - 1], "#");
9478             break;
9479         }
9480     }
9481 }
9482
9483
9484 /* Apply a move to the given board  */
9485 void
9486 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9487 {
9488   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9489   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9490
9491     /* [HGM] compute & store e.p. status and castling rights for new position */
9492     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9493
9494       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9495       oldEP = (signed char)board[EP_STATUS];
9496       board[EP_STATUS] = EP_NONE;
9497
9498   if (fromY == DROP_RANK) {
9499         /* must be first */
9500         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9501             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9502             return;
9503         }
9504         piece = board[toY][toX] = (ChessSquare) fromX;
9505   } else {
9506       int i;
9507
9508       if( board[toY][toX] != EmptySquare )
9509            board[EP_STATUS] = EP_CAPTURE;
9510
9511       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9512            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9513                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9514       } else
9515       if( board[fromY][fromX] == WhitePawn ) {
9516            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9517                board[EP_STATUS] = EP_PAWN_MOVE;
9518            if( toY-fromY==2) {
9519                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9520                         gameInfo.variant != VariantBerolina || toX < fromX)
9521                       board[EP_STATUS] = toX | berolina;
9522                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9523                         gameInfo.variant != VariantBerolina || toX > fromX)
9524                       board[EP_STATUS] = toX;
9525            }
9526       } else
9527       if( board[fromY][fromX] == BlackPawn ) {
9528            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9529                board[EP_STATUS] = EP_PAWN_MOVE;
9530            if( toY-fromY== -2) {
9531                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9532                         gameInfo.variant != VariantBerolina || toX < fromX)
9533                       board[EP_STATUS] = toX | berolina;
9534                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9535                         gameInfo.variant != VariantBerolina || toX > fromX)
9536                       board[EP_STATUS] = toX;
9537            }
9538        }
9539
9540        for(i=0; i<nrCastlingRights; i++) {
9541            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9542               board[CASTLING][i] == toX   && castlingRank[i] == toY
9543              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9544        }
9545
9546        if(gameInfo.variant == VariantSChess) { // update virginity
9547            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9548            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9549            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9550            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9551        }
9552
9553      if (fromX == toX && fromY == toY) return;
9554
9555      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9556      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9557      if(gameInfo.variant == VariantKnightmate)
9558          king += (int) WhiteUnicorn - (int) WhiteKing;
9559
9560     /* Code added by Tord: */
9561     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9562     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9563         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9564       board[fromY][fromX] = EmptySquare;
9565       board[toY][toX] = EmptySquare;
9566       if((toX > fromX) != (piece == WhiteRook)) {
9567         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9568       } else {
9569         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9570       }
9571     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9572                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9573       board[fromY][fromX] = EmptySquare;
9574       board[toY][toX] = EmptySquare;
9575       if((toX > fromX) != (piece == BlackRook)) {
9576         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9577       } else {
9578         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9579       }
9580     /* End of code added by Tord */
9581
9582     } else if (board[fromY][fromX] == king
9583         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9584         && toY == fromY && toX > fromX+1) {
9585         board[fromY][fromX] = EmptySquare;
9586         board[toY][toX] = king;
9587         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9588         board[fromY][BOARD_RGHT-1] = EmptySquare;
9589     } else if (board[fromY][fromX] == king
9590         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9591                && toY == fromY && toX < fromX-1) {
9592         board[fromY][fromX] = EmptySquare;
9593         board[toY][toX] = king;
9594         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9595         board[fromY][BOARD_LEFT] = EmptySquare;
9596     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9597                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9598                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9599                ) {
9600         /* white pawn promotion */
9601         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9602         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9603             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9604         board[fromY][fromX] = EmptySquare;
9605     } else if ((fromY >= BOARD_HEIGHT>>1)
9606                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9607                && (toX != fromX)
9608                && gameInfo.variant != VariantXiangqi
9609                && gameInfo.variant != VariantBerolina
9610                && (board[fromY][fromX] == WhitePawn)
9611                && (board[toY][toX] == EmptySquare)) {
9612         board[fromY][fromX] = EmptySquare;
9613         board[toY][toX] = WhitePawn;
9614         captured = board[toY - 1][toX];
9615         board[toY - 1][toX] = EmptySquare;
9616     } else if ((fromY == BOARD_HEIGHT-4)
9617                && (toX == fromX)
9618                && gameInfo.variant == VariantBerolina
9619                && (board[fromY][fromX] == WhitePawn)
9620                && (board[toY][toX] == EmptySquare)) {
9621         board[fromY][fromX] = EmptySquare;
9622         board[toY][toX] = WhitePawn;
9623         if(oldEP & EP_BEROLIN_A) {
9624                 captured = board[fromY][fromX-1];
9625                 board[fromY][fromX-1] = EmptySquare;
9626         }else{  captured = board[fromY][fromX+1];
9627                 board[fromY][fromX+1] = EmptySquare;
9628         }
9629     } else if (board[fromY][fromX] == king
9630         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9631                && toY == fromY && toX > fromX+1) {
9632         board[fromY][fromX] = EmptySquare;
9633         board[toY][toX] = king;
9634         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9635         board[fromY][BOARD_RGHT-1] = EmptySquare;
9636     } else if (board[fromY][fromX] == king
9637         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9638                && toY == fromY && toX < fromX-1) {
9639         board[fromY][fromX] = EmptySquare;
9640         board[toY][toX] = king;
9641         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9642         board[fromY][BOARD_LEFT] = EmptySquare;
9643     } else if (fromY == 7 && fromX == 3
9644                && board[fromY][fromX] == BlackKing
9645                && toY == 7 && toX == 5) {
9646         board[fromY][fromX] = EmptySquare;
9647         board[toY][toX] = BlackKing;
9648         board[fromY][7] = EmptySquare;
9649         board[toY][4] = BlackRook;
9650     } else if (fromY == 7 && fromX == 3
9651                && board[fromY][fromX] == BlackKing
9652                && toY == 7 && toX == 1) {
9653         board[fromY][fromX] = EmptySquare;
9654         board[toY][toX] = BlackKing;
9655         board[fromY][0] = EmptySquare;
9656         board[toY][2] = BlackRook;
9657     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9658                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9659                && toY < promoRank && promoChar
9660                ) {
9661         /* black pawn promotion */
9662         board[toY][toX] = CharToPiece(ToLower(promoChar));
9663         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9664             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9665         board[fromY][fromX] = EmptySquare;
9666     } else if ((fromY < BOARD_HEIGHT>>1)
9667                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9668                && (toX != fromX)
9669                && gameInfo.variant != VariantXiangqi
9670                && gameInfo.variant != VariantBerolina
9671                && (board[fromY][fromX] == BlackPawn)
9672                && (board[toY][toX] == EmptySquare)) {
9673         board[fromY][fromX] = EmptySquare;
9674         board[toY][toX] = BlackPawn;
9675         captured = board[toY + 1][toX];
9676         board[toY + 1][toX] = EmptySquare;
9677     } else if ((fromY == 3)
9678                && (toX == fromX)
9679                && gameInfo.variant == VariantBerolina
9680                && (board[fromY][fromX] == BlackPawn)
9681                && (board[toY][toX] == EmptySquare)) {
9682         board[fromY][fromX] = EmptySquare;
9683         board[toY][toX] = BlackPawn;
9684         if(oldEP & EP_BEROLIN_A) {
9685                 captured = board[fromY][fromX-1];
9686                 board[fromY][fromX-1] = EmptySquare;
9687         }else{  captured = board[fromY][fromX+1];
9688                 board[fromY][fromX+1] = EmptySquare;
9689         }
9690     } else {
9691         board[toY][toX] = board[fromY][fromX];
9692         board[fromY][fromX] = EmptySquare;
9693     }
9694   }
9695
9696     if (gameInfo.holdingsWidth != 0) {
9697
9698       /* !!A lot more code needs to be written to support holdings  */
9699       /* [HGM] OK, so I have written it. Holdings are stored in the */
9700       /* penultimate board files, so they are automaticlly stored   */
9701       /* in the game history.                                       */
9702       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9703                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9704         /* Delete from holdings, by decreasing count */
9705         /* and erasing image if necessary            */
9706         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9707         if(p < (int) BlackPawn) { /* white drop */
9708              p -= (int)WhitePawn;
9709                  p = PieceToNumber((ChessSquare)p);
9710              if(p >= gameInfo.holdingsSize) p = 0;
9711              if(--board[p][BOARD_WIDTH-2] <= 0)
9712                   board[p][BOARD_WIDTH-1] = EmptySquare;
9713              if((int)board[p][BOARD_WIDTH-2] < 0)
9714                         board[p][BOARD_WIDTH-2] = 0;
9715         } else {                  /* black drop */
9716              p -= (int)BlackPawn;
9717                  p = PieceToNumber((ChessSquare)p);
9718              if(p >= gameInfo.holdingsSize) p = 0;
9719              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9720                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9721              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9722                         board[BOARD_HEIGHT-1-p][1] = 0;
9723         }
9724       }
9725       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9726           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9727         /* [HGM] holdings: Add to holdings, if holdings exist */
9728         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9729                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9730                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9731         }
9732         p = (int) captured;
9733         if (p >= (int) BlackPawn) {
9734           p -= (int)BlackPawn;
9735           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9736                   /* in Shogi restore piece to its original  first */
9737                   captured = (ChessSquare) (DEMOTED captured);
9738                   p = DEMOTED p;
9739           }
9740           p = PieceToNumber((ChessSquare)p);
9741           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9742           board[p][BOARD_WIDTH-2]++;
9743           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9744         } else {
9745           p -= (int)WhitePawn;
9746           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9747                   captured = (ChessSquare) (DEMOTED captured);
9748                   p = DEMOTED p;
9749           }
9750           p = PieceToNumber((ChessSquare)p);
9751           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9752           board[BOARD_HEIGHT-1-p][1]++;
9753           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9754         }
9755       }
9756     } else if (gameInfo.variant == VariantAtomic) {
9757       if (captured != EmptySquare) {
9758         int y, x;
9759         for (y = toY-1; y <= toY+1; y++) {
9760           for (x = toX-1; x <= toX+1; x++) {
9761             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9762                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9763               board[y][x] = EmptySquare;
9764             }
9765           }
9766         }
9767         board[toY][toX] = EmptySquare;
9768       }
9769     }
9770     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9771         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9772     } else
9773     if(promoChar == '+') {
9774         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9775         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9776     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9777         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9778         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9779            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9780         board[toY][toX] = newPiece;
9781     }
9782     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9783                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9784         // [HGM] superchess: take promotion piece out of holdings
9785         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9786         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9787             if(!--board[k][BOARD_WIDTH-2])
9788                 board[k][BOARD_WIDTH-1] = EmptySquare;
9789         } else {
9790             if(!--board[BOARD_HEIGHT-1-k][1])
9791                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9792         }
9793     }
9794
9795 }
9796
9797 /* Updates forwardMostMove */
9798 void
9799 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9800 {
9801 //    forwardMostMove++; // [HGM] bare: moved downstream
9802
9803     (void) CoordsToAlgebraic(boards[forwardMostMove],
9804                              PosFlags(forwardMostMove),
9805                              fromY, fromX, toY, toX, promoChar,
9806                              parseList[forwardMostMove]);
9807
9808     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9809         int timeLeft; static int lastLoadFlag=0; int king, piece;
9810         piece = boards[forwardMostMove][fromY][fromX];
9811         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9812         if(gameInfo.variant == VariantKnightmate)
9813             king += (int) WhiteUnicorn - (int) WhiteKing;
9814         if(forwardMostMove == 0) {
9815             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9816                 fprintf(serverMoves, "%s;", UserName());
9817             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9818                 fprintf(serverMoves, "%s;", second.tidy);
9819             fprintf(serverMoves, "%s;", first.tidy);
9820             if(gameMode == MachinePlaysWhite)
9821                 fprintf(serverMoves, "%s;", UserName());
9822             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9823                 fprintf(serverMoves, "%s;", second.tidy);
9824         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9825         lastLoadFlag = loadFlag;
9826         // print base move
9827         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9828         // print castling suffix
9829         if( toY == fromY && piece == king ) {
9830             if(toX-fromX > 1)
9831                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9832             if(fromX-toX >1)
9833                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9834         }
9835         // e.p. suffix
9836         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9837              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9838              boards[forwardMostMove][toY][toX] == EmptySquare
9839              && fromX != toX && fromY != toY)
9840                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9841         // promotion suffix
9842         if(promoChar != NULLCHAR) {
9843             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9844                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9845                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9846             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9847         }
9848         if(!loadFlag) {
9849                 char buf[MOVE_LEN*2], *p; int len;
9850             fprintf(serverMoves, "/%d/%d",
9851                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9852             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9853             else                      timeLeft = blackTimeRemaining/1000;
9854             fprintf(serverMoves, "/%d", timeLeft);
9855                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9856                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9857                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9858                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9859             fprintf(serverMoves, "/%s", buf);
9860         }
9861         fflush(serverMoves);
9862     }
9863
9864     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9865         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9866       return;
9867     }
9868     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9869     if (commentList[forwardMostMove+1] != NULL) {
9870         free(commentList[forwardMostMove+1]);
9871         commentList[forwardMostMove+1] = NULL;
9872     }
9873     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9874     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9875     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9876     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9877     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9878     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9879     adjustedClock = FALSE;
9880     gameInfo.result = GameUnfinished;
9881     if (gameInfo.resultDetails != NULL) {
9882         free(gameInfo.resultDetails);
9883         gameInfo.resultDetails = NULL;
9884     }
9885     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9886                               moveList[forwardMostMove - 1]);
9887     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9888       case MT_NONE:
9889       case MT_STALEMATE:
9890       default:
9891         break;
9892       case MT_CHECK:
9893         if(gameInfo.variant != VariantShogi)
9894             strcat(parseList[forwardMostMove - 1], "+");
9895         break;
9896       case MT_CHECKMATE:
9897       case MT_STAINMATE:
9898         strcat(parseList[forwardMostMove - 1], "#");
9899         break;
9900     }
9901
9902 }
9903
9904 /* Updates currentMove if not pausing */
9905 void
9906 ShowMove (int fromX, int fromY, int toX, int toY)
9907 {
9908     int instant = (gameMode == PlayFromGameFile) ?
9909         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9910     if(appData.noGUI) return;
9911     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9912         if (!instant) {
9913             if (forwardMostMove == currentMove + 1) {
9914                 AnimateMove(boards[forwardMostMove - 1],
9915                             fromX, fromY, toX, toY);
9916             }
9917         }
9918         currentMove = forwardMostMove;
9919     }
9920
9921     if (instant) return;
9922
9923     DisplayMove(currentMove - 1);
9924     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9925             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9926                 SetHighlights(fromX, fromY, toX, toY);
9927             }
9928     }
9929     DrawPosition(FALSE, boards[currentMove]);
9930     DisplayBothClocks();
9931     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9932 }
9933
9934 void
9935 SendEgtPath (ChessProgramState *cps)
9936 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9937         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9938
9939         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9940
9941         while(*p) {
9942             char c, *q = name+1, *r, *s;
9943
9944             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9945             while(*p && *p != ',') *q++ = *p++;
9946             *q++ = ':'; *q = 0;
9947             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9948                 strcmp(name, ",nalimov:") == 0 ) {
9949                 // take nalimov path from the menu-changeable option first, if it is defined
9950               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9951                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9952             } else
9953             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9954                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9955                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9956                 s = r = StrStr(s, ":") + 1; // beginning of path info
9957                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9958                 c = *r; *r = 0;             // temporarily null-terminate path info
9959                     *--q = 0;               // strip of trailig ':' from name
9960                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9961                 *r = c;
9962                 SendToProgram(buf,cps);     // send egtbpath command for this format
9963             }
9964             if(*p == ',') p++; // read away comma to position for next format name
9965         }
9966 }
9967
9968 static int
9969 NonStandardBoardSize ()
9970 {
9971       /* [HGM] Awkward testing. Should really be a table */
9972       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9973       if( gameInfo.variant == VariantXiangqi )
9974            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9975       if( gameInfo.variant == VariantShogi )
9976            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9977       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9978            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9979       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9980           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9981            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9982       if( gameInfo.variant == VariantCourier )
9983            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9984       if( gameInfo.variant == VariantSuper )
9985            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9986       if( gameInfo.variant == VariantGreat )
9987            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9988       if( gameInfo.variant == VariantSChess )
9989            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9990       if( gameInfo.variant == VariantGrand )
9991            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9992       return overruled;
9993 }
9994
9995 void
9996 InitChessProgram (ChessProgramState *cps, int setup)
9997 /* setup needed to setup FRC opening position */
9998 {
9999     char buf[MSG_SIZ], b[MSG_SIZ];
10000     if (appData.noChessProgram) return;
10001     hintRequested = FALSE;
10002     bookRequested = FALSE;
10003
10004     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10005     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10006     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10007     if(cps->memSize) { /* [HGM] memory */
10008       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10009         SendToProgram(buf, cps);
10010     }
10011     SendEgtPath(cps); /* [HGM] EGT */
10012     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10013       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10014         SendToProgram(buf, cps);
10015     }
10016
10017     SendToProgram(cps->initString, cps);
10018     if (gameInfo.variant != VariantNormal &&
10019         gameInfo.variant != VariantLoadable
10020         /* [HGM] also send variant if board size non-standard */
10021         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10022                                             ) {
10023       char *v = VariantName(gameInfo.variant);
10024       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10025         /* [HGM] in protocol 1 we have to assume all variants valid */
10026         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10027         DisplayFatalError(buf, 0, 1);
10028         return;
10029       }
10030
10031       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10032         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10033                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10034            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10035            if(StrStr(cps->variants, b) == NULL) {
10036                // specific sized variant not known, check if general sizing allowed
10037                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10038                    if(StrStr(cps->variants, "boardsize") == NULL) {
10039                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10040                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10041                        DisplayFatalError(buf, 0, 1);
10042                        return;
10043                    }
10044                    /* [HGM] here we really should compare with the maximum supported board size */
10045                }
10046            }
10047       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10048       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10049       SendToProgram(buf, cps);
10050     }
10051     currentlyInitializedVariant = gameInfo.variant;
10052
10053     /* [HGM] send opening position in FRC to first engine */
10054     if(setup) {
10055           SendToProgram("force\n", cps);
10056           SendBoard(cps, 0);
10057           /* engine is now in force mode! Set flag to wake it up after first move. */
10058           setboardSpoiledMachineBlack = 1;
10059     }
10060
10061     if (cps->sendICS) {
10062       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10063       SendToProgram(buf, cps);
10064     }
10065     cps->maybeThinking = FALSE;
10066     cps->offeredDraw = 0;
10067     if (!appData.icsActive) {
10068         SendTimeControl(cps, movesPerSession, timeControl,
10069                         timeIncrement, appData.searchDepth,
10070                         searchTime);
10071     }
10072     if (appData.showThinking
10073         // [HGM] thinking: four options require thinking output to be sent
10074         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10075                                 ) {
10076         SendToProgram("post\n", cps);
10077     }
10078     SendToProgram("hard\n", cps);
10079     if (!appData.ponderNextMove) {
10080         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10081            it without being sure what state we are in first.  "hard"
10082            is not a toggle, so that one is OK.
10083          */
10084         SendToProgram("easy\n", cps);
10085     }
10086     if (cps->usePing) {
10087       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10088       SendToProgram(buf, cps);
10089     }
10090     cps->initDone = TRUE;
10091     ClearEngineOutputPane(cps == &second);
10092 }
10093
10094
10095 void
10096 ResendOptions (ChessProgramState *cps)
10097 { // send the stored value of the options
10098   int i;
10099   char buf[MSG_SIZ];
10100   Option *opt = cps->option;
10101   for(i=0; i<cps->nrOptions; i++, opt++) {
10102       switch(opt->type) {
10103         case Spin:
10104         case Slider:
10105         case CheckBox:
10106             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10107           break;
10108         case ComboBox:
10109           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10110           break;
10111         default:
10112             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10113           break;
10114         case Button:
10115         case SaveButton:
10116           continue;
10117       }
10118       SendToProgram(buf, cps);
10119   }
10120 }
10121
10122 void
10123 StartChessProgram (ChessProgramState *cps)
10124 {
10125     char buf[MSG_SIZ];
10126     int err;
10127
10128     if (appData.noChessProgram) return;
10129     cps->initDone = FALSE;
10130
10131     if (strcmp(cps->host, "localhost") == 0) {
10132         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10133     } else if (*appData.remoteShell == NULLCHAR) {
10134         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10135     } else {
10136         if (*appData.remoteUser == NULLCHAR) {
10137           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10138                     cps->program);
10139         } else {
10140           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10141                     cps->host, appData.remoteUser, cps->program);
10142         }
10143         err = StartChildProcess(buf, "", &cps->pr);
10144     }
10145
10146     if (err != 0) {
10147       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10148         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10149         if(cps != &first) return;
10150         appData.noChessProgram = TRUE;
10151         ThawUI();
10152         SetNCPMode();
10153 //      DisplayFatalError(buf, err, 1);
10154 //      cps->pr = NoProc;
10155 //      cps->isr = NULL;
10156         return;
10157     }
10158
10159     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10160     if (cps->protocolVersion > 1) {
10161       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10162       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10163         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10164         cps->comboCnt = 0;  //                and values of combo boxes
10165       }
10166       SendToProgram(buf, cps);
10167       if(cps->reload) ResendOptions(cps);
10168     } else {
10169       SendToProgram("xboard\n", cps);
10170     }
10171 }
10172
10173 void
10174 TwoMachinesEventIfReady P((void))
10175 {
10176   static int curMess = 0;
10177   if (first.lastPing != first.lastPong) {
10178     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10179     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10180     return;
10181   }
10182   if (second.lastPing != second.lastPong) {
10183     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10184     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10185     return;
10186   }
10187   DisplayMessage("", ""); curMess = 0;
10188   TwoMachinesEvent();
10189 }
10190
10191 char *
10192 MakeName (char *template)
10193 {
10194     time_t clock;
10195     struct tm *tm;
10196     static char buf[MSG_SIZ];
10197     char *p = buf;
10198     int i;
10199
10200     clock = time((time_t *)NULL);
10201     tm = localtime(&clock);
10202
10203     while(*p++ = *template++) if(p[-1] == '%') {
10204         switch(*template++) {
10205           case 0:   *p = 0; return buf;
10206           case 'Y': i = tm->tm_year+1900; break;
10207           case 'y': i = tm->tm_year-100; break;
10208           case 'M': i = tm->tm_mon+1; break;
10209           case 'd': i = tm->tm_mday; break;
10210           case 'h': i = tm->tm_hour; break;
10211           case 'm': i = tm->tm_min; break;
10212           case 's': i = tm->tm_sec; break;
10213           default:  i = 0;
10214         }
10215         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10216     }
10217     return buf;
10218 }
10219
10220 int
10221 CountPlayers (char *p)
10222 {
10223     int n = 0;
10224     while(p = strchr(p, '\n')) p++, n++; // count participants
10225     return n;
10226 }
10227
10228 FILE *
10229 WriteTourneyFile (char *results, FILE *f)
10230 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10231     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10232     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10233         // create a file with tournament description
10234         fprintf(f, "-participants {%s}\n", appData.participants);
10235         fprintf(f, "-seedBase %d\n", appData.seedBase);
10236         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10237         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10238         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10239         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10240         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10241         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10242         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10243         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10244         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10245         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10246         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10247         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10248         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10249         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10250         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10251         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10252         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10253         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10254         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10255         fprintf(f, "-smpCores %d\n", appData.smpCores);
10256         if(searchTime > 0)
10257                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10258         else {
10259                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10260                 fprintf(f, "-tc %s\n", appData.timeControl);
10261                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10262         }
10263         fprintf(f, "-results \"%s\"\n", results);
10264     }
10265     return f;
10266 }
10267
10268 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10269
10270 void
10271 Substitute (char *participants, int expunge)
10272 {
10273     int i, changed, changes=0, nPlayers=0;
10274     char *p, *q, *r, buf[MSG_SIZ];
10275     if(participants == NULL) return;
10276     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10277     r = p = participants; q = appData.participants;
10278     while(*p && *p == *q) {
10279         if(*p == '\n') r = p+1, nPlayers++;
10280         p++; q++;
10281     }
10282     if(*p) { // difference
10283         while(*p && *p++ != '\n');
10284         while(*q && *q++ != '\n');
10285       changed = nPlayers;
10286         changes = 1 + (strcmp(p, q) != 0);
10287     }
10288     if(changes == 1) { // a single engine mnemonic was changed
10289         q = r; while(*q) nPlayers += (*q++ == '\n');
10290         p = buf; while(*r && (*p = *r++) != '\n') p++;
10291         *p = NULLCHAR;
10292         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10293         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10294         if(mnemonic[i]) { // The substitute is valid
10295             FILE *f;
10296             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10297                 flock(fileno(f), LOCK_EX);
10298                 ParseArgsFromFile(f);
10299                 fseek(f, 0, SEEK_SET);
10300                 FREE(appData.participants); appData.participants = participants;
10301                 if(expunge) { // erase results of replaced engine
10302                     int len = strlen(appData.results), w, b, dummy;
10303                     for(i=0; i<len; i++) {
10304                         Pairing(i, nPlayers, &w, &b, &dummy);
10305                         if((w == changed || b == changed) && appData.results[i] == '*') {
10306                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10307                             fclose(f);
10308                             return;
10309                         }
10310                     }
10311                     for(i=0; i<len; i++) {
10312                         Pairing(i, nPlayers, &w, &b, &dummy);
10313                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10314                     }
10315                 }
10316                 WriteTourneyFile(appData.results, f);
10317                 fclose(f); // release lock
10318                 return;
10319             }
10320         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10321     }
10322     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10323     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10324     free(participants);
10325     return;
10326 }
10327
10328 int
10329 CheckPlayers (char *participants)
10330 {
10331         int i;
10332         char buf[MSG_SIZ], *p;
10333         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10334         while(p = strchr(participants, '\n')) {
10335             *p = NULLCHAR;
10336             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10337             if(!mnemonic[i]) {
10338                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10339                 *p = '\n';
10340                 DisplayError(buf, 0);
10341                 return 1;
10342             }
10343             *p = '\n';
10344             participants = p + 1;
10345         }
10346         return 0;
10347 }
10348
10349 int
10350 CreateTourney (char *name)
10351 {
10352         FILE *f;
10353         if(matchMode && strcmp(name, appData.tourneyFile)) {
10354              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10355         }
10356         if(name[0] == NULLCHAR) {
10357             if(appData.participants[0])
10358                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10359             return 0;
10360         }
10361         f = fopen(name, "r");
10362         if(f) { // file exists
10363             ASSIGN(appData.tourneyFile, name);
10364             ParseArgsFromFile(f); // parse it
10365         } else {
10366             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10367             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10368                 DisplayError(_("Not enough participants"), 0);
10369                 return 0;
10370             }
10371             if(CheckPlayers(appData.participants)) return 0;
10372             ASSIGN(appData.tourneyFile, name);
10373             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10374             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10375         }
10376         fclose(f);
10377         appData.noChessProgram = FALSE;
10378         appData.clockMode = TRUE;
10379         SetGNUMode();
10380         return 1;
10381 }
10382
10383 int
10384 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10385 {
10386     char buf[MSG_SIZ], *p, *q;
10387     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10388     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10389     skip = !all && group[0]; // if group requested, we start in skip mode
10390     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10391         p = names; q = buf; header = 0;
10392         while(*p && *p != '\n') *q++ = *p++;
10393         *q = 0;
10394         if(*p == '\n') p++;
10395         if(buf[0] == '#') {
10396             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10397             depth++; // we must be entering a new group
10398             if(all) continue; // suppress printing group headers when complete list requested
10399             header = 1;
10400             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10401         }
10402         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10403         if(engineList[i]) free(engineList[i]);
10404         engineList[i] = strdup(buf);
10405         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10406         if(engineMnemonic[i]) free(engineMnemonic[i]);
10407         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10408             strcat(buf, " (");
10409             sscanf(q + 8, "%s", buf + strlen(buf));
10410             strcat(buf, ")");
10411         }
10412         engineMnemonic[i] = strdup(buf);
10413         i++;
10414     }
10415     engineList[i] = engineMnemonic[i] = NULL;
10416     return i;
10417 }
10418
10419 // following implemented as macro to avoid type limitations
10420 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10421
10422 void
10423 SwapEngines (int n)
10424 {   // swap settings for first engine and other engine (so far only some selected options)
10425     int h;
10426     char *p;
10427     if(n == 0) return;
10428     SWAP(directory, p)
10429     SWAP(chessProgram, p)
10430     SWAP(isUCI, h)
10431     SWAP(hasOwnBookUCI, h)
10432     SWAP(protocolVersion, h)
10433     SWAP(reuse, h)
10434     SWAP(scoreIsAbsolute, h)
10435     SWAP(timeOdds, h)
10436     SWAP(logo, p)
10437     SWAP(pgnName, p)
10438     SWAP(pvSAN, h)
10439     SWAP(engOptions, p)
10440     SWAP(engInitString, p)
10441     SWAP(computerString, p)
10442     SWAP(features, p)
10443     SWAP(fenOverride, p)
10444     SWAP(NPS, h)
10445     SWAP(accumulateTC, h)
10446     SWAP(host, p)
10447 }
10448
10449 int
10450 GetEngineLine (char *s, int n)
10451 {
10452     int i;
10453     char buf[MSG_SIZ];
10454     extern char *icsNames;
10455     if(!s || !*s) return 0;
10456     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10457     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10458     if(!mnemonic[i]) return 0;
10459     if(n == 11) return 1; // just testing if there was a match
10460     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10461     if(n == 1) SwapEngines(n);
10462     ParseArgsFromString(buf);
10463     if(n == 1) SwapEngines(n);
10464     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10465         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10466         ParseArgsFromString(buf);
10467     }
10468     return 1;
10469 }
10470
10471 int
10472 SetPlayer (int player, char *p)
10473 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10474     int i;
10475     char buf[MSG_SIZ], *engineName;
10476     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10477     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10478     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10479     if(mnemonic[i]) {
10480         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10481         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10482         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10483         ParseArgsFromString(buf);
10484     } else { // no engine with this nickname is installed!
10485         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10486         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10487         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10488         ModeHighlight();
10489         DisplayError(buf, 0);
10490         return 0;
10491     }
10492     free(engineName);
10493     return i;
10494 }
10495
10496 char *recentEngines;
10497
10498 void
10499 RecentEngineEvent (int nr)
10500 {
10501     int n;
10502 //    SwapEngines(1); // bump first to second
10503 //    ReplaceEngine(&second, 1); // and load it there
10504     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10505     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10506     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10507         ReplaceEngine(&first, 0);
10508         FloatToFront(&appData.recentEngineList, command[n]);
10509     }
10510 }
10511
10512 int
10513 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10514 {   // determine players from game number
10515     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10516
10517     if(appData.tourneyType == 0) {
10518         roundsPerCycle = (nPlayers - 1) | 1;
10519         pairingsPerRound = nPlayers / 2;
10520     } else if(appData.tourneyType > 0) {
10521         roundsPerCycle = nPlayers - appData.tourneyType;
10522         pairingsPerRound = appData.tourneyType;
10523     }
10524     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10525     gamesPerCycle = gamesPerRound * roundsPerCycle;
10526     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10527     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10528     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10529     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10530     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10531     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10532
10533     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10534     if(appData.roundSync) *syncInterval = gamesPerRound;
10535
10536     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10537
10538     if(appData.tourneyType == 0) {
10539         if(curPairing == (nPlayers-1)/2 ) {
10540             *whitePlayer = curRound;
10541             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10542         } else {
10543             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10544             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10545             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10546             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10547         }
10548     } else if(appData.tourneyType > 1) {
10549         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10550         *whitePlayer = curRound + appData.tourneyType;
10551     } else if(appData.tourneyType > 0) {
10552         *whitePlayer = curPairing;
10553         *blackPlayer = curRound + appData.tourneyType;
10554     }
10555
10556     // take care of white/black alternation per round.
10557     // For cycles and games this is already taken care of by default, derived from matchGame!
10558     return curRound & 1;
10559 }
10560
10561 int
10562 NextTourneyGame (int nr, int *swapColors)
10563 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10564     char *p, *q;
10565     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10566     FILE *tf;
10567     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10568     tf = fopen(appData.tourneyFile, "r");
10569     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10570     ParseArgsFromFile(tf); fclose(tf);
10571     InitTimeControls(); // TC might be altered from tourney file
10572
10573     nPlayers = CountPlayers(appData.participants); // count participants
10574     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10575     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10576
10577     if(syncInterval) {
10578         p = q = appData.results;
10579         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10580         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10581             DisplayMessage(_("Waiting for other game(s)"),"");
10582             waitingForGame = TRUE;
10583             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10584             return 0;
10585         }
10586         waitingForGame = FALSE;
10587     }
10588
10589     if(appData.tourneyType < 0) {
10590         if(nr>=0 && !pairingReceived) {
10591             char buf[1<<16];
10592             if(pairing.pr == NoProc) {
10593                 if(!appData.pairingEngine[0]) {
10594                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10595                     return 0;
10596                 }
10597                 StartChessProgram(&pairing); // starts the pairing engine
10598             }
10599             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10600             SendToProgram(buf, &pairing);
10601             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10602             SendToProgram(buf, &pairing);
10603             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10604         }
10605         pairingReceived = 0;                              // ... so we continue here
10606         *swapColors = 0;
10607         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10608         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10609         matchGame = 1; roundNr = nr / syncInterval + 1;
10610     }
10611
10612     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10613
10614     // redefine engines, engine dir, etc.
10615     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10616     if(first.pr == NoProc) {
10617       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10618       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10619     }
10620     if(second.pr == NoProc) {
10621       SwapEngines(1);
10622       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10623       SwapEngines(1);         // and make that valid for second engine by swapping
10624       InitEngine(&second, 1);
10625     }
10626     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10627     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10628     return OK;
10629 }
10630
10631 void
10632 NextMatchGame ()
10633 {   // performs game initialization that does not invoke engines, and then tries to start the game
10634     int res, firstWhite, swapColors = 0;
10635     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10636     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
10637         char buf[MSG_SIZ];
10638         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10639         if(strcmp(buf, currentDebugFile)) { // name has changed
10640             FILE *f = fopen(buf, "w");
10641             if(f) { // if opening the new file failed, just keep using the old one
10642                 ASSIGN(currentDebugFile, buf);
10643                 fclose(debugFP);
10644                 debugFP = f;
10645             }
10646             if(appData.serverFileName) {
10647                 if(serverFP) fclose(serverFP);
10648                 serverFP = fopen(appData.serverFileName, "w");
10649                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10650                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10651             }
10652         }
10653     }
10654     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10655     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10656     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10657     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10658     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10659     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10660     Reset(FALSE, first.pr != NoProc);
10661     res = LoadGameOrPosition(matchGame); // setup game
10662     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10663     if(!res) return; // abort when bad game/pos file
10664     TwoMachinesEvent();
10665 }
10666
10667 void
10668 UserAdjudicationEvent (int result)
10669 {
10670     ChessMove gameResult = GameIsDrawn;
10671
10672     if( result > 0 ) {
10673         gameResult = WhiteWins;
10674     }
10675     else if( result < 0 ) {
10676         gameResult = BlackWins;
10677     }
10678
10679     if( gameMode == TwoMachinesPlay ) {
10680         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10681     }
10682 }
10683
10684
10685 // [HGM] save: calculate checksum of game to make games easily identifiable
10686 int
10687 StringCheckSum (char *s)
10688 {
10689         int i = 0;
10690         if(s==NULL) return 0;
10691         while(*s) i = i*259 + *s++;
10692         return i;
10693 }
10694
10695 int
10696 GameCheckSum ()
10697 {
10698         int i, sum=0;
10699         for(i=backwardMostMove; i<forwardMostMove; i++) {
10700                 sum += pvInfoList[i].depth;
10701                 sum += StringCheckSum(parseList[i]);
10702                 sum += StringCheckSum(commentList[i]);
10703                 sum *= 261;
10704         }
10705         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10706         return sum + StringCheckSum(commentList[i]);
10707 } // end of save patch
10708
10709 void
10710 GameEnds (ChessMove result, char *resultDetails, int whosays)
10711 {
10712     GameMode nextGameMode;
10713     int isIcsGame;
10714     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10715
10716     if(endingGame) return; /* [HGM] crash: forbid recursion */
10717     endingGame = 1;
10718     if(twoBoards) { // [HGM] dual: switch back to one board
10719         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10720         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10721     }
10722     if (appData.debugMode) {
10723       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10724               result, resultDetails ? resultDetails : "(null)", whosays);
10725     }
10726
10727     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10728
10729     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10730
10731     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10732         /* If we are playing on ICS, the server decides when the
10733            game is over, but the engine can offer to draw, claim
10734            a draw, or resign.
10735          */
10736 #if ZIPPY
10737         if (appData.zippyPlay && first.initDone) {
10738             if (result == GameIsDrawn) {
10739                 /* In case draw still needs to be claimed */
10740                 SendToICS(ics_prefix);
10741                 SendToICS("draw\n");
10742             } else if (StrCaseStr(resultDetails, "resign")) {
10743                 SendToICS(ics_prefix);
10744                 SendToICS("resign\n");
10745             }
10746         }
10747 #endif
10748         endingGame = 0; /* [HGM] crash */
10749         return;
10750     }
10751
10752     /* If we're loading the game from a file, stop */
10753     if (whosays == GE_FILE) {
10754       (void) StopLoadGameTimer();
10755       gameFileFP = NULL;
10756     }
10757
10758     /* Cancel draw offers */
10759     first.offeredDraw = second.offeredDraw = 0;
10760
10761     /* If this is an ICS game, only ICS can really say it's done;
10762        if not, anyone can. */
10763     isIcsGame = (gameMode == IcsPlayingWhite ||
10764                  gameMode == IcsPlayingBlack ||
10765                  gameMode == IcsObserving    ||
10766                  gameMode == IcsExamining);
10767
10768     if (!isIcsGame || whosays == GE_ICS) {
10769         /* OK -- not an ICS game, or ICS said it was done */
10770         StopClocks();
10771         if (!isIcsGame && !appData.noChessProgram)
10772           SetUserThinkingEnables();
10773
10774         /* [HGM] if a machine claims the game end we verify this claim */
10775         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10776             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10777                 char claimer;
10778                 ChessMove trueResult = (ChessMove) -1;
10779
10780                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10781                                             first.twoMachinesColor[0] :
10782                                             second.twoMachinesColor[0] ;
10783
10784                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10785                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10786                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10787                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10788                 } else
10789                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10790                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10791                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10792                 } else
10793                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10794                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10795                 }
10796
10797                 // now verify win claims, but not in drop games, as we don't understand those yet
10798                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10799                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10800                     (result == WhiteWins && claimer == 'w' ||
10801                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10802                       if (appData.debugMode) {
10803                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10804                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10805                       }
10806                       if(result != trueResult) {
10807                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10808                               result = claimer == 'w' ? BlackWins : WhiteWins;
10809                               resultDetails = buf;
10810                       }
10811                 } else
10812                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10813                     && (forwardMostMove <= backwardMostMove ||
10814                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10815                         (claimer=='b')==(forwardMostMove&1))
10816                                                                                   ) {
10817                       /* [HGM] verify: draws that were not flagged are false claims */
10818                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10819                       result = claimer == 'w' ? BlackWins : WhiteWins;
10820                       resultDetails = buf;
10821                 }
10822                 /* (Claiming a loss is accepted no questions asked!) */
10823             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10824                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10825                 result = GameUnfinished;
10826                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10827             }
10828             /* [HGM] bare: don't allow bare King to win */
10829             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10830                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10831                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10832                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10833                && result != GameIsDrawn)
10834             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10835                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10836                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10837                         if(p >= 0 && p <= (int)WhiteKing) k++;
10838                 }
10839                 if (appData.debugMode) {
10840                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10841                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10842                 }
10843                 if(k <= 1) {
10844                         result = GameIsDrawn;
10845                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10846                         resultDetails = buf;
10847                 }
10848             }
10849         }
10850
10851
10852         if(serverMoves != NULL && !loadFlag) { char c = '=';
10853             if(result==WhiteWins) c = '+';
10854             if(result==BlackWins) c = '-';
10855             if(resultDetails != NULL)
10856                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10857         }
10858         if (resultDetails != NULL) {
10859             gameInfo.result = result;
10860             gameInfo.resultDetails = StrSave(resultDetails);
10861
10862             /* display last move only if game was not loaded from file */
10863             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10864                 DisplayMove(currentMove - 1);
10865
10866             if (forwardMostMove != 0) {
10867                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10868                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10869                                                                 ) {
10870                     if (*appData.saveGameFile != NULLCHAR) {
10871                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10872                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10873                         else
10874                         SaveGameToFile(appData.saveGameFile, TRUE);
10875                     } else if (appData.autoSaveGames) {
10876                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10877                     }
10878                     if (*appData.savePositionFile != NULLCHAR) {
10879                         SavePositionToFile(appData.savePositionFile);
10880                     }
10881                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10882                 }
10883             }
10884
10885             /* Tell program how game ended in case it is learning */
10886             /* [HGM] Moved this to after saving the PGN, just in case */
10887             /* engine died and we got here through time loss. In that */
10888             /* case we will get a fatal error writing the pipe, which */
10889             /* would otherwise lose us the PGN.                       */
10890             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10891             /* output during GameEnds should never be fatal anymore   */
10892             if (gameMode == MachinePlaysWhite ||
10893                 gameMode == MachinePlaysBlack ||
10894                 gameMode == TwoMachinesPlay ||
10895                 gameMode == IcsPlayingWhite ||
10896                 gameMode == IcsPlayingBlack ||
10897                 gameMode == BeginningOfGame) {
10898                 char buf[MSG_SIZ];
10899                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10900                         resultDetails);
10901                 if (first.pr != NoProc) {
10902                     SendToProgram(buf, &first);
10903                 }
10904                 if (second.pr != NoProc &&
10905                     gameMode == TwoMachinesPlay) {
10906                     SendToProgram(buf, &second);
10907                 }
10908             }
10909         }
10910
10911         if (appData.icsActive) {
10912             if (appData.quietPlay &&
10913                 (gameMode == IcsPlayingWhite ||
10914                  gameMode == IcsPlayingBlack)) {
10915                 SendToICS(ics_prefix);
10916                 SendToICS("set shout 1\n");
10917             }
10918             nextGameMode = IcsIdle;
10919             ics_user_moved = FALSE;
10920             /* clean up premove.  It's ugly when the game has ended and the
10921              * premove highlights are still on the board.
10922              */
10923             if (gotPremove) {
10924               gotPremove = FALSE;
10925               ClearPremoveHighlights();
10926               DrawPosition(FALSE, boards[currentMove]);
10927             }
10928             if (whosays == GE_ICS) {
10929                 switch (result) {
10930                 case WhiteWins:
10931                     if (gameMode == IcsPlayingWhite)
10932                         PlayIcsWinSound();
10933                     else if(gameMode == IcsPlayingBlack)
10934                         PlayIcsLossSound();
10935                     break;
10936                 case BlackWins:
10937                     if (gameMode == IcsPlayingBlack)
10938                         PlayIcsWinSound();
10939                     else if(gameMode == IcsPlayingWhite)
10940                         PlayIcsLossSound();
10941                     break;
10942                 case GameIsDrawn:
10943                     PlayIcsDrawSound();
10944                     break;
10945                 default:
10946                     PlayIcsUnfinishedSound();
10947                 }
10948             }
10949             if(appData.quitNext) { ExitEvent(0); return; }
10950         } else if (gameMode == EditGame ||
10951                    gameMode == PlayFromGameFile ||
10952                    gameMode == AnalyzeMode ||
10953                    gameMode == AnalyzeFile) {
10954             nextGameMode = gameMode;
10955         } else {
10956             nextGameMode = EndOfGame;
10957         }
10958         pausing = FALSE;
10959         ModeHighlight();
10960     } else {
10961         nextGameMode = gameMode;
10962     }
10963
10964     if (appData.noChessProgram) {
10965         gameMode = nextGameMode;
10966         ModeHighlight();
10967         endingGame = 0; /* [HGM] crash */
10968         return;
10969     }
10970
10971     if (first.reuse) {
10972         /* Put first chess program into idle state */
10973         if (first.pr != NoProc &&
10974             (gameMode == MachinePlaysWhite ||
10975              gameMode == MachinePlaysBlack ||
10976              gameMode == TwoMachinesPlay ||
10977              gameMode == IcsPlayingWhite ||
10978              gameMode == IcsPlayingBlack ||
10979              gameMode == BeginningOfGame)) {
10980             SendToProgram("force\n", &first);
10981             if (first.usePing) {
10982               char buf[MSG_SIZ];
10983               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10984               SendToProgram(buf, &first);
10985             }
10986         }
10987     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10988         /* Kill off first chess program */
10989         if (first.isr != NULL)
10990           RemoveInputSource(first.isr);
10991         first.isr = NULL;
10992
10993         if (first.pr != NoProc) {
10994             ExitAnalyzeMode();
10995             DoSleep( appData.delayBeforeQuit );
10996             SendToProgram("quit\n", &first);
10997             DoSleep( appData.delayAfterQuit );
10998             DestroyChildProcess(first.pr, first.useSigterm);
10999             first.reload = TRUE;
11000         }
11001         first.pr = NoProc;
11002     }
11003     if (second.reuse) {
11004         /* Put second chess program into idle state */
11005         if (second.pr != NoProc &&
11006             gameMode == TwoMachinesPlay) {
11007             SendToProgram("force\n", &second);
11008             if (second.usePing) {
11009               char buf[MSG_SIZ];
11010               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11011               SendToProgram(buf, &second);
11012             }
11013         }
11014     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11015         /* Kill off second chess program */
11016         if (second.isr != NULL)
11017           RemoveInputSource(second.isr);
11018         second.isr = NULL;
11019
11020         if (second.pr != NoProc) {
11021             DoSleep( appData.delayBeforeQuit );
11022             SendToProgram("quit\n", &second);
11023             DoSleep( appData.delayAfterQuit );
11024             DestroyChildProcess(second.pr, second.useSigterm);
11025             second.reload = TRUE;
11026         }
11027         second.pr = NoProc;
11028     }
11029
11030     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11031         char resChar = '=';
11032         switch (result) {
11033         case WhiteWins:
11034           resChar = '+';
11035           if (first.twoMachinesColor[0] == 'w') {
11036             first.matchWins++;
11037           } else {
11038             second.matchWins++;
11039           }
11040           break;
11041         case BlackWins:
11042           resChar = '-';
11043           if (first.twoMachinesColor[0] == 'b') {
11044             first.matchWins++;
11045           } else {
11046             second.matchWins++;
11047           }
11048           break;
11049         case GameUnfinished:
11050           resChar = ' ';
11051         default:
11052           break;
11053         }
11054
11055         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11056         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11057             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11058             ReserveGame(nextGame, resChar); // sets nextGame
11059             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11060             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11061         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11062
11063         if (nextGame <= appData.matchGames && !abortMatch) {
11064             gameMode = nextGameMode;
11065             matchGame = nextGame; // this will be overruled in tourney mode!
11066             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11067             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11068             endingGame = 0; /* [HGM] crash */
11069             return;
11070         } else {
11071             gameMode = nextGameMode;
11072             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11073                      first.tidy, second.tidy,
11074                      first.matchWins, second.matchWins,
11075                      appData.matchGames - (first.matchWins + second.matchWins));
11076             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11077             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11078             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11079             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11080                 first.twoMachinesColor = "black\n";
11081                 second.twoMachinesColor = "white\n";
11082             } else {
11083                 first.twoMachinesColor = "white\n";
11084                 second.twoMachinesColor = "black\n";
11085             }
11086         }
11087     }
11088     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11089         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11090       ExitAnalyzeMode();
11091     gameMode = nextGameMode;
11092     ModeHighlight();
11093     endingGame = 0;  /* [HGM] crash */
11094     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11095         if(matchMode == TRUE) { // match through command line: exit with or without popup
11096             if(ranking) {
11097                 ToNrEvent(forwardMostMove);
11098                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11099                 else ExitEvent(0);
11100             } else DisplayFatalError(buf, 0, 0);
11101         } else { // match through menu; just stop, with or without popup
11102             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11103             ModeHighlight();
11104             if(ranking){
11105                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11106             } else DisplayNote(buf);
11107       }
11108       if(ranking) free(ranking);
11109     }
11110 }
11111
11112 /* Assumes program was just initialized (initString sent).
11113    Leaves program in force mode. */
11114 void
11115 FeedMovesToProgram (ChessProgramState *cps, int upto)
11116 {
11117     int i;
11118
11119     if (appData.debugMode)
11120       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11121               startedFromSetupPosition ? "position and " : "",
11122               backwardMostMove, upto, cps->which);
11123     if(currentlyInitializedVariant != gameInfo.variant) {
11124       char buf[MSG_SIZ];
11125         // [HGM] variantswitch: make engine aware of new variant
11126         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11127                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11128         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11129         SendToProgram(buf, cps);
11130         currentlyInitializedVariant = gameInfo.variant;
11131     }
11132     SendToProgram("force\n", cps);
11133     if (startedFromSetupPosition) {
11134         SendBoard(cps, backwardMostMove);
11135     if (appData.debugMode) {
11136         fprintf(debugFP, "feedMoves\n");
11137     }
11138     }
11139     for (i = backwardMostMove; i < upto; i++) {
11140         SendMoveToProgram(i, cps);
11141     }
11142 }
11143
11144
11145 int
11146 ResurrectChessProgram ()
11147 {
11148      /* The chess program may have exited.
11149         If so, restart it and feed it all the moves made so far. */
11150     static int doInit = 0;
11151
11152     if (appData.noChessProgram) return 1;
11153
11154     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11155         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11156         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11157         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11158     } else {
11159         if (first.pr != NoProc) return 1;
11160         StartChessProgram(&first);
11161     }
11162     InitChessProgram(&first, FALSE);
11163     FeedMovesToProgram(&first, currentMove);
11164
11165     if (!first.sendTime) {
11166         /* can't tell gnuchess what its clock should read,
11167            so we bow to its notion. */
11168         ResetClocks();
11169         timeRemaining[0][currentMove] = whiteTimeRemaining;
11170         timeRemaining[1][currentMove] = blackTimeRemaining;
11171     }
11172
11173     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11174                 appData.icsEngineAnalyze) && first.analysisSupport) {
11175       SendToProgram("analyze\n", &first);
11176       first.analyzing = TRUE;
11177     }
11178     return 1;
11179 }
11180
11181 /*
11182  * Button procedures
11183  */
11184 void
11185 Reset (int redraw, int init)
11186 {
11187     int i;
11188
11189     if (appData.debugMode) {
11190         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11191                 redraw, init, gameMode);
11192     }
11193     CleanupTail(); // [HGM] vari: delete any stored variations
11194     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11195     pausing = pauseExamInvalid = FALSE;
11196     startedFromSetupPosition = blackPlaysFirst = FALSE;
11197     firstMove = TRUE;
11198     whiteFlag = blackFlag = FALSE;
11199     userOfferedDraw = FALSE;
11200     hintRequested = bookRequested = FALSE;
11201     first.maybeThinking = FALSE;
11202     second.maybeThinking = FALSE;
11203     first.bookSuspend = FALSE; // [HGM] book
11204     second.bookSuspend = FALSE;
11205     thinkOutput[0] = NULLCHAR;
11206     lastHint[0] = NULLCHAR;
11207     ClearGameInfo(&gameInfo);
11208     gameInfo.variant = StringToVariant(appData.variant);
11209     ics_user_moved = ics_clock_paused = FALSE;
11210     ics_getting_history = H_FALSE;
11211     ics_gamenum = -1;
11212     white_holding[0] = black_holding[0] = NULLCHAR;
11213     ClearProgramStats();
11214     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11215
11216     ResetFrontEnd();
11217     ClearHighlights();
11218     flipView = appData.flipView;
11219     ClearPremoveHighlights();
11220     gotPremove = FALSE;
11221     alarmSounded = FALSE;
11222
11223     GameEnds(EndOfFile, NULL, GE_PLAYER);
11224     if(appData.serverMovesName != NULL) {
11225         /* [HGM] prepare to make moves file for broadcasting */
11226         clock_t t = clock();
11227         if(serverMoves != NULL) fclose(serverMoves);
11228         serverMoves = fopen(appData.serverMovesName, "r");
11229         if(serverMoves != NULL) {
11230             fclose(serverMoves);
11231             /* delay 15 sec before overwriting, so all clients can see end */
11232             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11233         }
11234         serverMoves = fopen(appData.serverMovesName, "w");
11235     }
11236
11237     ExitAnalyzeMode();
11238     gameMode = BeginningOfGame;
11239     ModeHighlight();
11240     if(appData.icsActive) gameInfo.variant = VariantNormal;
11241     currentMove = forwardMostMove = backwardMostMove = 0;
11242     MarkTargetSquares(1);
11243     InitPosition(redraw);
11244     for (i = 0; i < MAX_MOVES; i++) {
11245         if (commentList[i] != NULL) {
11246             free(commentList[i]);
11247             commentList[i] = NULL;
11248         }
11249     }
11250     ResetClocks();
11251     timeRemaining[0][0] = whiteTimeRemaining;
11252     timeRemaining[1][0] = blackTimeRemaining;
11253
11254     if (first.pr == NoProc) {
11255         StartChessProgram(&first);
11256     }
11257     if (init) {
11258             InitChessProgram(&first, startedFromSetupPosition);
11259     }
11260     DisplayTitle("");
11261     DisplayMessage("", "");
11262     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11263     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11264     ClearMap();        // [HGM] exclude: invalidate map
11265 }
11266
11267 void
11268 AutoPlayGameLoop ()
11269 {
11270     for (;;) {
11271         if (!AutoPlayOneMove())
11272           return;
11273         if (matchMode || appData.timeDelay == 0)
11274           continue;
11275         if (appData.timeDelay < 0)
11276           return;
11277         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11278         break;
11279     }
11280 }
11281
11282 void
11283 AnalyzeNextGame()
11284 {
11285     ReloadGame(1); // next game
11286 }
11287
11288 int
11289 AutoPlayOneMove ()
11290 {
11291     int fromX, fromY, toX, toY;
11292
11293     if (appData.debugMode) {
11294       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11295     }
11296
11297     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11298       return FALSE;
11299
11300     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11301       pvInfoList[currentMove].depth = programStats.depth;
11302       pvInfoList[currentMove].score = programStats.score;
11303       pvInfoList[currentMove].time  = 0;
11304       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11305       else { // append analysis of final position as comment
11306         char buf[MSG_SIZ];
11307         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11308         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11309       }
11310       programStats.depth = 0;
11311     }
11312
11313     if (currentMove >= forwardMostMove) {
11314       if(gameMode == AnalyzeFile) {
11315           if(appData.loadGameIndex == -1) {
11316             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11317           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11318           } else {
11319           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11320         }
11321       }
11322 //      gameMode = EndOfGame;
11323 //      ModeHighlight();
11324
11325       /* [AS] Clear current move marker at the end of a game */
11326       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11327
11328       return FALSE;
11329     }
11330
11331     toX = moveList[currentMove][2] - AAA;
11332     toY = moveList[currentMove][3] - ONE;
11333
11334     if (moveList[currentMove][1] == '@') {
11335         if (appData.highlightLastMove) {
11336             SetHighlights(-1, -1, toX, toY);
11337         }
11338     } else {
11339         fromX = moveList[currentMove][0] - AAA;
11340         fromY = moveList[currentMove][1] - ONE;
11341
11342         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11343
11344         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11345
11346         if (appData.highlightLastMove) {
11347             SetHighlights(fromX, fromY, toX, toY);
11348         }
11349     }
11350     DisplayMove(currentMove);
11351     SendMoveToProgram(currentMove++, &first);
11352     DisplayBothClocks();
11353     DrawPosition(FALSE, boards[currentMove]);
11354     // [HGM] PV info: always display, routine tests if empty
11355     DisplayComment(currentMove - 1, commentList[currentMove]);
11356     return TRUE;
11357 }
11358
11359
11360 int
11361 LoadGameOneMove (ChessMove readAhead)
11362 {
11363     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11364     char promoChar = NULLCHAR;
11365     ChessMove moveType;
11366     char move[MSG_SIZ];
11367     char *p, *q;
11368
11369     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11370         gameMode != AnalyzeMode && gameMode != Training) {
11371         gameFileFP = NULL;
11372         return FALSE;
11373     }
11374
11375     yyboardindex = forwardMostMove;
11376     if (readAhead != EndOfFile) {
11377       moveType = readAhead;
11378     } else {
11379       if (gameFileFP == NULL)
11380           return FALSE;
11381       moveType = (ChessMove) Myylex();
11382     }
11383
11384     done = FALSE;
11385     switch (moveType) {
11386       case Comment:
11387         if (appData.debugMode)
11388           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11389         p = yy_text;
11390
11391         /* append the comment but don't display it */
11392         AppendComment(currentMove, p, FALSE);
11393         return TRUE;
11394
11395       case WhiteCapturesEnPassant:
11396       case BlackCapturesEnPassant:
11397       case WhitePromotion:
11398       case BlackPromotion:
11399       case WhiteNonPromotion:
11400       case BlackNonPromotion:
11401       case NormalMove:
11402       case WhiteKingSideCastle:
11403       case WhiteQueenSideCastle:
11404       case BlackKingSideCastle:
11405       case BlackQueenSideCastle:
11406       case WhiteKingSideCastleWild:
11407       case WhiteQueenSideCastleWild:
11408       case BlackKingSideCastleWild:
11409       case BlackQueenSideCastleWild:
11410       /* PUSH Fabien */
11411       case WhiteHSideCastleFR:
11412       case WhiteASideCastleFR:
11413       case BlackHSideCastleFR:
11414       case BlackASideCastleFR:
11415       /* POP Fabien */
11416         if (appData.debugMode)
11417           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11418         fromX = currentMoveString[0] - AAA;
11419         fromY = currentMoveString[1] - ONE;
11420         toX = currentMoveString[2] - AAA;
11421         toY = currentMoveString[3] - ONE;
11422         promoChar = currentMoveString[4];
11423         break;
11424
11425       case WhiteDrop:
11426       case BlackDrop:
11427         if (appData.debugMode)
11428           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11429         fromX = moveType == WhiteDrop ?
11430           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11431         (int) CharToPiece(ToLower(currentMoveString[0]));
11432         fromY = DROP_RANK;
11433         toX = currentMoveString[2] - AAA;
11434         toY = currentMoveString[3] - ONE;
11435         break;
11436
11437       case WhiteWins:
11438       case BlackWins:
11439       case GameIsDrawn:
11440       case GameUnfinished:
11441         if (appData.debugMode)
11442           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11443         p = strchr(yy_text, '{');
11444         if (p == NULL) p = strchr(yy_text, '(');
11445         if (p == NULL) {
11446             p = yy_text;
11447             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11448         } else {
11449             q = strchr(p, *p == '{' ? '}' : ')');
11450             if (q != NULL) *q = NULLCHAR;
11451             p++;
11452         }
11453         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11454         GameEnds(moveType, p, GE_FILE);
11455         done = TRUE;
11456         if (cmailMsgLoaded) {
11457             ClearHighlights();
11458             flipView = WhiteOnMove(currentMove);
11459             if (moveType == GameUnfinished) flipView = !flipView;
11460             if (appData.debugMode)
11461               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11462         }
11463         break;
11464
11465       case EndOfFile:
11466         if (appData.debugMode)
11467           fprintf(debugFP, "Parser hit end of file\n");
11468         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11469           case MT_NONE:
11470           case MT_CHECK:
11471             break;
11472           case MT_CHECKMATE:
11473           case MT_STAINMATE:
11474             if (WhiteOnMove(currentMove)) {
11475                 GameEnds(BlackWins, "Black mates", GE_FILE);
11476             } else {
11477                 GameEnds(WhiteWins, "White mates", GE_FILE);
11478             }
11479             break;
11480           case MT_STALEMATE:
11481             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11482             break;
11483         }
11484         done = TRUE;
11485         break;
11486
11487       case MoveNumberOne:
11488         if (lastLoadGameStart == GNUChessGame) {
11489             /* GNUChessGames have numbers, but they aren't move numbers */
11490             if (appData.debugMode)
11491               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11492                       yy_text, (int) moveType);
11493             return LoadGameOneMove(EndOfFile); /* tail recursion */
11494         }
11495         /* else fall thru */
11496
11497       case XBoardGame:
11498       case GNUChessGame:
11499       case PGNTag:
11500         /* Reached start of next game in file */
11501         if (appData.debugMode)
11502           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11503         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11504           case MT_NONE:
11505           case MT_CHECK:
11506             break;
11507           case MT_CHECKMATE:
11508           case MT_STAINMATE:
11509             if (WhiteOnMove(currentMove)) {
11510                 GameEnds(BlackWins, "Black mates", GE_FILE);
11511             } else {
11512                 GameEnds(WhiteWins, "White mates", GE_FILE);
11513             }
11514             break;
11515           case MT_STALEMATE:
11516             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11517             break;
11518         }
11519         done = TRUE;
11520         break;
11521
11522       case PositionDiagram:     /* should not happen; ignore */
11523       case ElapsedTime:         /* ignore */
11524       case NAG:                 /* ignore */
11525         if (appData.debugMode)
11526           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11527                   yy_text, (int) moveType);
11528         return LoadGameOneMove(EndOfFile); /* tail recursion */
11529
11530       case IllegalMove:
11531         if (appData.testLegality) {
11532             if (appData.debugMode)
11533               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11534             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11535                     (forwardMostMove / 2) + 1,
11536                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11537             DisplayError(move, 0);
11538             done = TRUE;
11539         } else {
11540             if (appData.debugMode)
11541               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11542                       yy_text, currentMoveString);
11543             fromX = currentMoveString[0] - AAA;
11544             fromY = currentMoveString[1] - ONE;
11545             toX = currentMoveString[2] - AAA;
11546             toY = currentMoveString[3] - ONE;
11547             promoChar = currentMoveString[4];
11548         }
11549         break;
11550
11551       case AmbiguousMove:
11552         if (appData.debugMode)
11553           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11554         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11555                 (forwardMostMove / 2) + 1,
11556                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11557         DisplayError(move, 0);
11558         done = TRUE;
11559         break;
11560
11561       default:
11562       case ImpossibleMove:
11563         if (appData.debugMode)
11564           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11565         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11566                 (forwardMostMove / 2) + 1,
11567                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11568         DisplayError(move, 0);
11569         done = TRUE;
11570         break;
11571     }
11572
11573     if (done) {
11574         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11575             DrawPosition(FALSE, boards[currentMove]);
11576             DisplayBothClocks();
11577             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11578               DisplayComment(currentMove - 1, commentList[currentMove]);
11579         }
11580         (void) StopLoadGameTimer();
11581         gameFileFP = NULL;
11582         cmailOldMove = forwardMostMove;
11583         return FALSE;
11584     } else {
11585         /* currentMoveString is set as a side-effect of yylex */
11586
11587         thinkOutput[0] = NULLCHAR;
11588         MakeMove(fromX, fromY, toX, toY, promoChar);
11589         currentMove = forwardMostMove;
11590         return TRUE;
11591     }
11592 }
11593
11594 /* Load the nth game from the given file */
11595 int
11596 LoadGameFromFile (char *filename, int n, char *title, int useList)
11597 {
11598     FILE *f;
11599     char buf[MSG_SIZ];
11600
11601     if (strcmp(filename, "-") == 0) {
11602         f = stdin;
11603         title = "stdin";
11604     } else {
11605         f = fopen(filename, "rb");
11606         if (f == NULL) {
11607           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11608             DisplayError(buf, errno);
11609             return FALSE;
11610         }
11611     }
11612     if (fseek(f, 0, 0) == -1) {
11613         /* f is not seekable; probably a pipe */
11614         useList = FALSE;
11615     }
11616     if (useList && n == 0) {
11617         int error = GameListBuild(f);
11618         if (error) {
11619             DisplayError(_("Cannot build game list"), error);
11620         } else if (!ListEmpty(&gameList) &&
11621                    ((ListGame *) gameList.tailPred)->number > 1) {
11622             GameListPopUp(f, title);
11623             return TRUE;
11624         }
11625         GameListDestroy();
11626         n = 1;
11627     }
11628     if (n == 0) n = 1;
11629     return LoadGame(f, n, title, FALSE);
11630 }
11631
11632
11633 void
11634 MakeRegisteredMove ()
11635 {
11636     int fromX, fromY, toX, toY;
11637     char promoChar;
11638     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11639         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11640           case CMAIL_MOVE:
11641           case CMAIL_DRAW:
11642             if (appData.debugMode)
11643               fprintf(debugFP, "Restoring %s for game %d\n",
11644                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11645
11646             thinkOutput[0] = NULLCHAR;
11647             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11648             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11649             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11650             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11651             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11652             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11653             MakeMove(fromX, fromY, toX, toY, promoChar);
11654             ShowMove(fromX, fromY, toX, toY);
11655
11656             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11657               case MT_NONE:
11658               case MT_CHECK:
11659                 break;
11660
11661               case MT_CHECKMATE:
11662               case MT_STAINMATE:
11663                 if (WhiteOnMove(currentMove)) {
11664                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11665                 } else {
11666                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11667                 }
11668                 break;
11669
11670               case MT_STALEMATE:
11671                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11672                 break;
11673             }
11674
11675             break;
11676
11677           case CMAIL_RESIGN:
11678             if (WhiteOnMove(currentMove)) {
11679                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11680             } else {
11681                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11682             }
11683             break;
11684
11685           case CMAIL_ACCEPT:
11686             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11687             break;
11688
11689           default:
11690             break;
11691         }
11692     }
11693
11694     return;
11695 }
11696
11697 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11698 int
11699 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11700 {
11701     int retVal;
11702
11703     if (gameNumber > nCmailGames) {
11704         DisplayError(_("No more games in this message"), 0);
11705         return FALSE;
11706     }
11707     if (f == lastLoadGameFP) {
11708         int offset = gameNumber - lastLoadGameNumber;
11709         if (offset == 0) {
11710             cmailMsg[0] = NULLCHAR;
11711             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11712                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11713                 nCmailMovesRegistered--;
11714             }
11715             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11716             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11717                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11718             }
11719         } else {
11720             if (! RegisterMove()) return FALSE;
11721         }
11722     }
11723
11724     retVal = LoadGame(f, gameNumber, title, useList);
11725
11726     /* Make move registered during previous look at this game, if any */
11727     MakeRegisteredMove();
11728
11729     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11730         commentList[currentMove]
11731           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11732         DisplayComment(currentMove - 1, commentList[currentMove]);
11733     }
11734
11735     return retVal;
11736 }
11737
11738 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11739 int
11740 ReloadGame (int offset)
11741 {
11742     int gameNumber = lastLoadGameNumber + offset;
11743     if (lastLoadGameFP == NULL) {
11744         DisplayError(_("No game has been loaded yet"), 0);
11745         return FALSE;
11746     }
11747     if (gameNumber <= 0) {
11748         DisplayError(_("Can't back up any further"), 0);
11749         return FALSE;
11750     }
11751     if (cmailMsgLoaded) {
11752         return CmailLoadGame(lastLoadGameFP, gameNumber,
11753                              lastLoadGameTitle, lastLoadGameUseList);
11754     } else {
11755         return LoadGame(lastLoadGameFP, gameNumber,
11756                         lastLoadGameTitle, lastLoadGameUseList);
11757     }
11758 }
11759
11760 int keys[EmptySquare+1];
11761
11762 int
11763 PositionMatches (Board b1, Board b2)
11764 {
11765     int r, f, sum=0;
11766     switch(appData.searchMode) {
11767         case 1: return CompareWithRights(b1, b2);
11768         case 2:
11769             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11770                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11771             }
11772             return TRUE;
11773         case 3:
11774             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11775               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11776                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11777             }
11778             return sum==0;
11779         case 4:
11780             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11781                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11782             }
11783             return sum==0;
11784     }
11785     return TRUE;
11786 }
11787
11788 #define Q_PROMO  4
11789 #define Q_EP     3
11790 #define Q_BCASTL 2
11791 #define Q_WCASTL 1
11792
11793 int pieceList[256], quickBoard[256];
11794 ChessSquare pieceType[256] = { EmptySquare };
11795 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11796 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11797 int soughtTotal, turn;
11798 Boolean epOK, flipSearch;
11799
11800 typedef struct {
11801     unsigned char piece, to;
11802 } Move;
11803
11804 #define DSIZE (250000)
11805
11806 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11807 Move *moveDatabase = initialSpace;
11808 unsigned int movePtr, dataSize = DSIZE;
11809
11810 int
11811 MakePieceList (Board board, int *counts)
11812 {
11813     int r, f, n=Q_PROMO, total=0;
11814     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11815     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11816         int sq = f + (r<<4);
11817         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11818             quickBoard[sq] = ++n;
11819             pieceList[n] = sq;
11820             pieceType[n] = board[r][f];
11821             counts[board[r][f]]++;
11822             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11823             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11824             total++;
11825         }
11826     }
11827     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11828     return total;
11829 }
11830
11831 void
11832 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11833 {
11834     int sq = fromX + (fromY<<4);
11835     int piece = quickBoard[sq];
11836     quickBoard[sq] = 0;
11837     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11838     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11839         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11840         moveDatabase[movePtr++].piece = Q_WCASTL;
11841         quickBoard[sq] = piece;
11842         piece = quickBoard[from]; quickBoard[from] = 0;
11843         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11844     } else
11845     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11846         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11847         moveDatabase[movePtr++].piece = Q_BCASTL;
11848         quickBoard[sq] = piece;
11849         piece = quickBoard[from]; quickBoard[from] = 0;
11850         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11851     } else
11852     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11853         quickBoard[(fromY<<4)+toX] = 0;
11854         moveDatabase[movePtr].piece = Q_EP;
11855         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11856         moveDatabase[movePtr].to = sq;
11857     } else
11858     if(promoPiece != pieceType[piece]) {
11859         moveDatabase[movePtr++].piece = Q_PROMO;
11860         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11861     }
11862     moveDatabase[movePtr].piece = piece;
11863     quickBoard[sq] = piece;
11864     movePtr++;
11865 }
11866
11867 int
11868 PackGame (Board board)
11869 {
11870     Move *newSpace = NULL;
11871     moveDatabase[movePtr].piece = 0; // terminate previous game
11872     if(movePtr > dataSize) {
11873         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11874         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11875         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11876         if(newSpace) {
11877             int i;
11878             Move *p = moveDatabase, *q = newSpace;
11879             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11880             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11881             moveDatabase = newSpace;
11882         } else { // calloc failed, we must be out of memory. Too bad...
11883             dataSize = 0; // prevent calloc events for all subsequent games
11884             return 0;     // and signal this one isn't cached
11885         }
11886     }
11887     movePtr++;
11888     MakePieceList(board, counts);
11889     return movePtr;
11890 }
11891
11892 int
11893 QuickCompare (Board board, int *minCounts, int *maxCounts)
11894 {   // compare according to search mode
11895     int r, f;
11896     switch(appData.searchMode)
11897     {
11898       case 1: // exact position match
11899         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11900         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11901             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11902         }
11903         break;
11904       case 2: // can have extra material on empty squares
11905         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11906             if(board[r][f] == EmptySquare) continue;
11907             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11908         }
11909         break;
11910       case 3: // material with exact Pawn structure
11911         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11912             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11913             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11914         } // fall through to material comparison
11915       case 4: // exact material
11916         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11917         break;
11918       case 6: // material range with given imbalance
11919         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11920         // fall through to range comparison
11921       case 5: // material range
11922         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11923     }
11924     return TRUE;
11925 }
11926
11927 int
11928 QuickScan (Board board, Move *move)
11929 {   // reconstruct game,and compare all positions in it
11930     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11931     do {
11932         int piece = move->piece;
11933         int to = move->to, from = pieceList[piece];
11934         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11935           if(!piece) return -1;
11936           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11937             piece = (++move)->piece;
11938             from = pieceList[piece];
11939             counts[pieceType[piece]]--;
11940             pieceType[piece] = (ChessSquare) move->to;
11941             counts[move->to]++;
11942           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11943             counts[pieceType[quickBoard[to]]]--;
11944             quickBoard[to] = 0; total--;
11945             move++;
11946             continue;
11947           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11948             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11949             from  = pieceList[piece]; // so this must be King
11950             quickBoard[from] = 0;
11951             pieceList[piece] = to;
11952             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11953             quickBoard[from] = 0; // rook
11954             quickBoard[to] = piece;
11955             to = move->to; piece = move->piece;
11956             goto aftercastle;
11957           }
11958         }
11959         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11960         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11961         quickBoard[from] = 0;
11962       aftercastle:
11963         quickBoard[to] = piece;
11964         pieceList[piece] = to;
11965         cnt++; turn ^= 3;
11966         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11967            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11968            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11969                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11970           ) {
11971             static int lastCounts[EmptySquare+1];
11972             int i;
11973             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11974             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11975         } else stretch = 0;
11976         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11977         move++;
11978     } while(1);
11979 }
11980
11981 void
11982 InitSearch ()
11983 {
11984     int r, f;
11985     flipSearch = FALSE;
11986     CopyBoard(soughtBoard, boards[currentMove]);
11987     soughtTotal = MakePieceList(soughtBoard, maxSought);
11988     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11989     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11990     CopyBoard(reverseBoard, boards[currentMove]);
11991     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11992         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11993         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11994         reverseBoard[r][f] = piece;
11995     }
11996     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11997     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11998     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11999                  || (boards[currentMove][CASTLING][2] == NoRights ||
12000                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12001                  && (boards[currentMove][CASTLING][5] == NoRights ||
12002                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12003       ) {
12004         flipSearch = TRUE;
12005         CopyBoard(flipBoard, soughtBoard);
12006         CopyBoard(rotateBoard, reverseBoard);
12007         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12008             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12009             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12010         }
12011     }
12012     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12013     if(appData.searchMode >= 5) {
12014         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12015         MakePieceList(soughtBoard, minSought);
12016         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12017     }
12018     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12019         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12020 }
12021
12022 GameInfo dummyInfo;
12023 static int creatingBook;
12024
12025 int
12026 GameContainsPosition (FILE *f, ListGame *lg)
12027 {
12028     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12029     int fromX, fromY, toX, toY;
12030     char promoChar;
12031     static int initDone=FALSE;
12032
12033     // weed out games based on numerical tag comparison
12034     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12035     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12036     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12037     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12038     if(!initDone) {
12039         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12040         initDone = TRUE;
12041     }
12042     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
12043     else CopyBoard(boards[scratch], initialPosition); // default start position
12044     if(lg->moves) {
12045         turn = btm + 1;
12046         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12047         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12048     }
12049     if(btm) plyNr++;
12050     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12051     fseek(f, lg->offset, 0);
12052     yynewfile(f);
12053     while(1) {
12054         yyboardindex = scratch;
12055         quickFlag = plyNr+1;
12056         next = Myylex();
12057         quickFlag = 0;
12058         switch(next) {
12059             case PGNTag:
12060                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12061             default:
12062                 continue;
12063
12064             case XBoardGame:
12065             case GNUChessGame:
12066                 if(plyNr) return -1; // after we have seen moves, this is for new game
12067               continue;
12068
12069             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12070             case ImpossibleMove:
12071             case WhiteWins: // game ends here with these four
12072             case BlackWins:
12073             case GameIsDrawn:
12074             case GameUnfinished:
12075                 return -1;
12076
12077             case IllegalMove:
12078                 if(appData.testLegality) return -1;
12079             case WhiteCapturesEnPassant:
12080             case BlackCapturesEnPassant:
12081             case WhitePromotion:
12082             case BlackPromotion:
12083             case WhiteNonPromotion:
12084             case BlackNonPromotion:
12085             case NormalMove:
12086             case WhiteKingSideCastle:
12087             case WhiteQueenSideCastle:
12088             case BlackKingSideCastle:
12089             case BlackQueenSideCastle:
12090             case WhiteKingSideCastleWild:
12091             case WhiteQueenSideCastleWild:
12092             case BlackKingSideCastleWild:
12093             case BlackQueenSideCastleWild:
12094             case WhiteHSideCastleFR:
12095             case WhiteASideCastleFR:
12096             case BlackHSideCastleFR:
12097             case BlackASideCastleFR:
12098                 fromX = currentMoveString[0] - AAA;
12099                 fromY = currentMoveString[1] - ONE;
12100                 toX = currentMoveString[2] - AAA;
12101                 toY = currentMoveString[3] - ONE;
12102                 promoChar = currentMoveString[4];
12103                 break;
12104             case WhiteDrop:
12105             case BlackDrop:
12106                 fromX = next == WhiteDrop ?
12107                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12108                   (int) CharToPiece(ToLower(currentMoveString[0]));
12109                 fromY = DROP_RANK;
12110                 toX = currentMoveString[2] - AAA;
12111                 toY = currentMoveString[3] - ONE;
12112                 promoChar = 0;
12113                 break;
12114         }
12115         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12116         plyNr++;
12117         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12118         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12119         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12120         if(appData.findMirror) {
12121             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12122             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12123         }
12124     }
12125 }
12126
12127 /* Load the nth game from open file f */
12128 int
12129 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12130 {
12131     ChessMove cm;
12132     char buf[MSG_SIZ];
12133     int gn = gameNumber;
12134     ListGame *lg = NULL;
12135     int numPGNTags = 0;
12136     int err, pos = -1;
12137     GameMode oldGameMode;
12138     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12139
12140     if (appData.debugMode)
12141         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12142
12143     if (gameMode == Training )
12144         SetTrainingModeOff();
12145
12146     oldGameMode = gameMode;
12147     if (gameMode != BeginningOfGame) {
12148       Reset(FALSE, TRUE);
12149     }
12150
12151     gameFileFP = f;
12152     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12153         fclose(lastLoadGameFP);
12154     }
12155
12156     if (useList) {
12157         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12158
12159         if (lg) {
12160             fseek(f, lg->offset, 0);
12161             GameListHighlight(gameNumber);
12162             pos = lg->position;
12163             gn = 1;
12164         }
12165         else {
12166             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12167               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12168             else
12169             DisplayError(_("Game number out of range"), 0);
12170             return FALSE;
12171         }
12172     } else {
12173         GameListDestroy();
12174         if (fseek(f, 0, 0) == -1) {
12175             if (f == lastLoadGameFP ?
12176                 gameNumber == lastLoadGameNumber + 1 :
12177                 gameNumber == 1) {
12178                 gn = 1;
12179             } else {
12180                 DisplayError(_("Can't seek on game file"), 0);
12181                 return FALSE;
12182             }
12183         }
12184     }
12185     lastLoadGameFP = f;
12186     lastLoadGameNumber = gameNumber;
12187     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12188     lastLoadGameUseList = useList;
12189
12190     yynewfile(f);
12191
12192     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12193       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12194                 lg->gameInfo.black);
12195             DisplayTitle(buf);
12196     } else if (*title != NULLCHAR) {
12197         if (gameNumber > 1) {
12198           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12199             DisplayTitle(buf);
12200         } else {
12201             DisplayTitle(title);
12202         }
12203     }
12204
12205     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12206         gameMode = PlayFromGameFile;
12207         ModeHighlight();
12208     }
12209
12210     currentMove = forwardMostMove = backwardMostMove = 0;
12211     CopyBoard(boards[0], initialPosition);
12212     StopClocks();
12213
12214     /*
12215      * Skip the first gn-1 games in the file.
12216      * Also skip over anything that precedes an identifiable
12217      * start of game marker, to avoid being confused by
12218      * garbage at the start of the file.  Currently
12219      * recognized start of game markers are the move number "1",
12220      * the pattern "gnuchess .* game", the pattern
12221      * "^[#;%] [^ ]* game file", and a PGN tag block.
12222      * A game that starts with one of the latter two patterns
12223      * will also have a move number 1, possibly
12224      * following a position diagram.
12225      * 5-4-02: Let's try being more lenient and allowing a game to
12226      * start with an unnumbered move.  Does that break anything?
12227      */
12228     cm = lastLoadGameStart = EndOfFile;
12229     while (gn > 0) {
12230         yyboardindex = forwardMostMove;
12231         cm = (ChessMove) Myylex();
12232         switch (cm) {
12233           case EndOfFile:
12234             if (cmailMsgLoaded) {
12235                 nCmailGames = CMAIL_MAX_GAMES - gn;
12236             } else {
12237                 Reset(TRUE, TRUE);
12238                 DisplayError(_("Game not found in file"), 0);
12239             }
12240             return FALSE;
12241
12242           case GNUChessGame:
12243           case XBoardGame:
12244             gn--;
12245             lastLoadGameStart = cm;
12246             break;
12247
12248           case MoveNumberOne:
12249             switch (lastLoadGameStart) {
12250               case GNUChessGame:
12251               case XBoardGame:
12252               case PGNTag:
12253                 break;
12254               case MoveNumberOne:
12255               case EndOfFile:
12256                 gn--;           /* count this game */
12257                 lastLoadGameStart = cm;
12258                 break;
12259               default:
12260                 /* impossible */
12261                 break;
12262             }
12263             break;
12264
12265           case PGNTag:
12266             switch (lastLoadGameStart) {
12267               case GNUChessGame:
12268               case PGNTag:
12269               case MoveNumberOne:
12270               case EndOfFile:
12271                 gn--;           /* count this game */
12272                 lastLoadGameStart = cm;
12273                 break;
12274               case XBoardGame:
12275                 lastLoadGameStart = cm; /* game counted already */
12276                 break;
12277               default:
12278                 /* impossible */
12279                 break;
12280             }
12281             if (gn > 0) {
12282                 do {
12283                     yyboardindex = forwardMostMove;
12284                     cm = (ChessMove) Myylex();
12285                 } while (cm == PGNTag || cm == Comment);
12286             }
12287             break;
12288
12289           case WhiteWins:
12290           case BlackWins:
12291           case GameIsDrawn:
12292             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12293                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12294                     != CMAIL_OLD_RESULT) {
12295                     nCmailResults ++ ;
12296                     cmailResult[  CMAIL_MAX_GAMES
12297                                 - gn - 1] = CMAIL_OLD_RESULT;
12298                 }
12299             }
12300             break;
12301
12302           case NormalMove:
12303             /* Only a NormalMove can be at the start of a game
12304              * without a position diagram. */
12305             if (lastLoadGameStart == EndOfFile ) {
12306               gn--;
12307               lastLoadGameStart = MoveNumberOne;
12308             }
12309             break;
12310
12311           default:
12312             break;
12313         }
12314     }
12315
12316     if (appData.debugMode)
12317       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12318
12319     if (cm == XBoardGame) {
12320         /* Skip any header junk before position diagram and/or move 1 */
12321         for (;;) {
12322             yyboardindex = forwardMostMove;
12323             cm = (ChessMove) Myylex();
12324
12325             if (cm == EndOfFile ||
12326                 cm == GNUChessGame || cm == XBoardGame) {
12327                 /* Empty game; pretend end-of-file and handle later */
12328                 cm = EndOfFile;
12329                 break;
12330             }
12331
12332             if (cm == MoveNumberOne || cm == PositionDiagram ||
12333                 cm == PGNTag || cm == Comment)
12334               break;
12335         }
12336     } else if (cm == GNUChessGame) {
12337         if (gameInfo.event != NULL) {
12338             free(gameInfo.event);
12339         }
12340         gameInfo.event = StrSave(yy_text);
12341     }
12342
12343     startedFromSetupPosition = FALSE;
12344     while (cm == PGNTag) {
12345         if (appData.debugMode)
12346           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12347         err = ParsePGNTag(yy_text, &gameInfo);
12348         if (!err) numPGNTags++;
12349
12350         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12351         if(gameInfo.variant != oldVariant) {
12352             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12353             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12354             InitPosition(TRUE);
12355             oldVariant = gameInfo.variant;
12356             if (appData.debugMode)
12357               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12358         }
12359
12360
12361         if (gameInfo.fen != NULL) {
12362           Board initial_position;
12363           startedFromSetupPosition = TRUE;
12364           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12365             Reset(TRUE, TRUE);
12366             DisplayError(_("Bad FEN position in file"), 0);
12367             return FALSE;
12368           }
12369           CopyBoard(boards[0], initial_position);
12370           if (blackPlaysFirst) {
12371             currentMove = forwardMostMove = backwardMostMove = 1;
12372             CopyBoard(boards[1], initial_position);
12373             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12374             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12375             timeRemaining[0][1] = whiteTimeRemaining;
12376             timeRemaining[1][1] = blackTimeRemaining;
12377             if (commentList[0] != NULL) {
12378               commentList[1] = commentList[0];
12379               commentList[0] = NULL;
12380             }
12381           } else {
12382             currentMove = forwardMostMove = backwardMostMove = 0;
12383           }
12384           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12385           {   int i;
12386               initialRulePlies = FENrulePlies;
12387               for( i=0; i< nrCastlingRights; i++ )
12388                   initialRights[i] = initial_position[CASTLING][i];
12389           }
12390           yyboardindex = forwardMostMove;
12391           free(gameInfo.fen);
12392           gameInfo.fen = NULL;
12393         }
12394
12395         yyboardindex = forwardMostMove;
12396         cm = (ChessMove) Myylex();
12397
12398         /* Handle comments interspersed among the tags */
12399         while (cm == Comment) {
12400             char *p;
12401             if (appData.debugMode)
12402               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12403             p = yy_text;
12404             AppendComment(currentMove, p, FALSE);
12405             yyboardindex = forwardMostMove;
12406             cm = (ChessMove) Myylex();
12407         }
12408     }
12409
12410     /* don't rely on existence of Event tag since if game was
12411      * pasted from clipboard the Event tag may not exist
12412      */
12413     if (numPGNTags > 0){
12414         char *tags;
12415         if (gameInfo.variant == VariantNormal) {
12416           VariantClass v = StringToVariant(gameInfo.event);
12417           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12418           if(v < VariantShogi) gameInfo.variant = v;
12419         }
12420         if (!matchMode) {
12421           if( appData.autoDisplayTags ) {
12422             tags = PGNTags(&gameInfo);
12423             TagsPopUp(tags, CmailMsg());
12424             free(tags);
12425           }
12426         }
12427     } else {
12428         /* Make something up, but don't display it now */
12429         SetGameInfo();
12430         TagsPopDown();
12431     }
12432
12433     if (cm == PositionDiagram) {
12434         int i, j;
12435         char *p;
12436         Board initial_position;
12437
12438         if (appData.debugMode)
12439           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12440
12441         if (!startedFromSetupPosition) {
12442             p = yy_text;
12443             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12444               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12445                 switch (*p) {
12446                   case '{':
12447                   case '[':
12448                   case '-':
12449                   case ' ':
12450                   case '\t':
12451                   case '\n':
12452                   case '\r':
12453                     break;
12454                   default:
12455                     initial_position[i][j++] = CharToPiece(*p);
12456                     break;
12457                 }
12458             while (*p == ' ' || *p == '\t' ||
12459                    *p == '\n' || *p == '\r') p++;
12460
12461             if (strncmp(p, "black", strlen("black"))==0)
12462               blackPlaysFirst = TRUE;
12463             else
12464               blackPlaysFirst = FALSE;
12465             startedFromSetupPosition = TRUE;
12466
12467             CopyBoard(boards[0], initial_position);
12468             if (blackPlaysFirst) {
12469                 currentMove = forwardMostMove = backwardMostMove = 1;
12470                 CopyBoard(boards[1], initial_position);
12471                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12472                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12473                 timeRemaining[0][1] = whiteTimeRemaining;
12474                 timeRemaining[1][1] = blackTimeRemaining;
12475                 if (commentList[0] != NULL) {
12476                     commentList[1] = commentList[0];
12477                     commentList[0] = NULL;
12478                 }
12479             } else {
12480                 currentMove = forwardMostMove = backwardMostMove = 0;
12481             }
12482         }
12483         yyboardindex = forwardMostMove;
12484         cm = (ChessMove) Myylex();
12485     }
12486
12487   if(!creatingBook) {
12488     if (first.pr == NoProc) {
12489         StartChessProgram(&first);
12490     }
12491     InitChessProgram(&first, FALSE);
12492     SendToProgram("force\n", &first);
12493     if (startedFromSetupPosition) {
12494         SendBoard(&first, forwardMostMove);
12495     if (appData.debugMode) {
12496         fprintf(debugFP, "Load Game\n");
12497     }
12498         DisplayBothClocks();
12499     }
12500   }
12501
12502     /* [HGM] server: flag to write setup moves in broadcast file as one */
12503     loadFlag = appData.suppressLoadMoves;
12504
12505     while (cm == Comment) {
12506         char *p;
12507         if (appData.debugMode)
12508           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12509         p = yy_text;
12510         AppendComment(currentMove, p, FALSE);
12511         yyboardindex = forwardMostMove;
12512         cm = (ChessMove) Myylex();
12513     }
12514
12515     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12516         cm == WhiteWins || cm == BlackWins ||
12517         cm == GameIsDrawn || cm == GameUnfinished) {
12518         DisplayMessage("", _("No moves in game"));
12519         if (cmailMsgLoaded) {
12520             if (appData.debugMode)
12521               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12522             ClearHighlights();
12523             flipView = FALSE;
12524         }
12525         DrawPosition(FALSE, boards[currentMove]);
12526         DisplayBothClocks();
12527         gameMode = EditGame;
12528         ModeHighlight();
12529         gameFileFP = NULL;
12530         cmailOldMove = 0;
12531         return TRUE;
12532     }
12533
12534     // [HGM] PV info: routine tests if comment empty
12535     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12536         DisplayComment(currentMove - 1, commentList[currentMove]);
12537     }
12538     if (!matchMode && appData.timeDelay != 0)
12539       DrawPosition(FALSE, boards[currentMove]);
12540
12541     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12542       programStats.ok_to_send = 1;
12543     }
12544
12545     /* if the first token after the PGN tags is a move
12546      * and not move number 1, retrieve it from the parser
12547      */
12548     if (cm != MoveNumberOne)
12549         LoadGameOneMove(cm);
12550
12551     /* load the remaining moves from the file */
12552     while (LoadGameOneMove(EndOfFile)) {
12553       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12554       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12555     }
12556
12557     /* rewind to the start of the game */
12558     currentMove = backwardMostMove;
12559
12560     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12561
12562     if (oldGameMode == AnalyzeFile) {
12563       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12564       AnalyzeFileEvent();
12565     } else
12566     if (oldGameMode == AnalyzeMode) {
12567       AnalyzeFileEvent();
12568     }
12569
12570     if(creatingBook) return TRUE;
12571     if (!matchMode && pos > 0) {
12572         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12573     } else
12574     if (matchMode || appData.timeDelay == 0) {
12575       ToEndEvent();
12576     } else if (appData.timeDelay > 0) {
12577       AutoPlayGameLoop();
12578     }
12579
12580     if (appData.debugMode)
12581         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12582
12583     loadFlag = 0; /* [HGM] true game starts */
12584     return TRUE;
12585 }
12586
12587 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12588 int
12589 ReloadPosition (int offset)
12590 {
12591     int positionNumber = lastLoadPositionNumber + offset;
12592     if (lastLoadPositionFP == NULL) {
12593         DisplayError(_("No position has been loaded yet"), 0);
12594         return FALSE;
12595     }
12596     if (positionNumber <= 0) {
12597         DisplayError(_("Can't back up any further"), 0);
12598         return FALSE;
12599     }
12600     return LoadPosition(lastLoadPositionFP, positionNumber,
12601                         lastLoadPositionTitle);
12602 }
12603
12604 /* Load the nth position from the given file */
12605 int
12606 LoadPositionFromFile (char *filename, int n, char *title)
12607 {
12608     FILE *f;
12609     char buf[MSG_SIZ];
12610
12611     if (strcmp(filename, "-") == 0) {
12612         return LoadPosition(stdin, n, "stdin");
12613     } else {
12614         f = fopen(filename, "rb");
12615         if (f == NULL) {
12616             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12617             DisplayError(buf, errno);
12618             return FALSE;
12619         } else {
12620             return LoadPosition(f, n, title);
12621         }
12622     }
12623 }
12624
12625 /* Load the nth position from the given open file, and close it */
12626 int
12627 LoadPosition (FILE *f, int positionNumber, char *title)
12628 {
12629     char *p, line[MSG_SIZ];
12630     Board initial_position;
12631     int i, j, fenMode, pn;
12632
12633     if (gameMode == Training )
12634         SetTrainingModeOff();
12635
12636     if (gameMode != BeginningOfGame) {
12637         Reset(FALSE, TRUE);
12638     }
12639     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12640         fclose(lastLoadPositionFP);
12641     }
12642     if (positionNumber == 0) positionNumber = 1;
12643     lastLoadPositionFP = f;
12644     lastLoadPositionNumber = positionNumber;
12645     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12646     if (first.pr == NoProc && !appData.noChessProgram) {
12647       StartChessProgram(&first);
12648       InitChessProgram(&first, FALSE);
12649     }
12650     pn = positionNumber;
12651     if (positionNumber < 0) {
12652         /* Negative position number means to seek to that byte offset */
12653         if (fseek(f, -positionNumber, 0) == -1) {
12654             DisplayError(_("Can't seek on position file"), 0);
12655             return FALSE;
12656         };
12657         pn = 1;
12658     } else {
12659         if (fseek(f, 0, 0) == -1) {
12660             if (f == lastLoadPositionFP ?
12661                 positionNumber == lastLoadPositionNumber + 1 :
12662                 positionNumber == 1) {
12663                 pn = 1;
12664             } else {
12665                 DisplayError(_("Can't seek on position file"), 0);
12666                 return FALSE;
12667             }
12668         }
12669     }
12670     /* See if this file is FEN or old-style xboard */
12671     if (fgets(line, MSG_SIZ, f) == NULL) {
12672         DisplayError(_("Position not found in file"), 0);
12673         return FALSE;
12674     }
12675     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12676     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12677
12678     if (pn >= 2) {
12679         if (fenMode || line[0] == '#') pn--;
12680         while (pn > 0) {
12681             /* skip positions before number pn */
12682             if (fgets(line, MSG_SIZ, f) == NULL) {
12683                 Reset(TRUE, TRUE);
12684                 DisplayError(_("Position not found in file"), 0);
12685                 return FALSE;
12686             }
12687             if (fenMode || line[0] == '#') pn--;
12688         }
12689     }
12690
12691     if (fenMode) {
12692         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12693             DisplayError(_("Bad FEN position in file"), 0);
12694             return FALSE;
12695         }
12696     } else {
12697         (void) fgets(line, MSG_SIZ, f);
12698         (void) fgets(line, MSG_SIZ, f);
12699
12700         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12701             (void) fgets(line, MSG_SIZ, f);
12702             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12703                 if (*p == ' ')
12704                   continue;
12705                 initial_position[i][j++] = CharToPiece(*p);
12706             }
12707         }
12708
12709         blackPlaysFirst = FALSE;
12710         if (!feof(f)) {
12711             (void) fgets(line, MSG_SIZ, f);
12712             if (strncmp(line, "black", strlen("black"))==0)
12713               blackPlaysFirst = TRUE;
12714         }
12715     }
12716     startedFromSetupPosition = TRUE;
12717
12718     CopyBoard(boards[0], initial_position);
12719     if (blackPlaysFirst) {
12720         currentMove = forwardMostMove = backwardMostMove = 1;
12721         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12722         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12723         CopyBoard(boards[1], initial_position);
12724         DisplayMessage("", _("Black to play"));
12725     } else {
12726         currentMove = forwardMostMove = backwardMostMove = 0;
12727         DisplayMessage("", _("White to play"));
12728     }
12729     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12730     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12731         SendToProgram("force\n", &first);
12732         SendBoard(&first, forwardMostMove);
12733     }
12734     if (appData.debugMode) {
12735 int i, j;
12736   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12737   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12738         fprintf(debugFP, "Load Position\n");
12739     }
12740
12741     if (positionNumber > 1) {
12742       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12743         DisplayTitle(line);
12744     } else {
12745         DisplayTitle(title);
12746     }
12747     gameMode = EditGame;
12748     ModeHighlight();
12749     ResetClocks();
12750     timeRemaining[0][1] = whiteTimeRemaining;
12751     timeRemaining[1][1] = blackTimeRemaining;
12752     DrawPosition(FALSE, boards[currentMove]);
12753
12754     return TRUE;
12755 }
12756
12757
12758 void
12759 CopyPlayerNameIntoFileName (char **dest, char *src)
12760 {
12761     while (*src != NULLCHAR && *src != ',') {
12762         if (*src == ' ') {
12763             *(*dest)++ = '_';
12764             src++;
12765         } else {
12766             *(*dest)++ = *src++;
12767         }
12768     }
12769 }
12770
12771 char *
12772 DefaultFileName (char *ext)
12773 {
12774     static char def[MSG_SIZ];
12775     char *p;
12776
12777     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12778         p = def;
12779         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12780         *p++ = '-';
12781         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12782         *p++ = '.';
12783         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12784     } else {
12785         def[0] = NULLCHAR;
12786     }
12787     return def;
12788 }
12789
12790 /* Save the current game to the given file */
12791 int
12792 SaveGameToFile (char *filename, int append)
12793 {
12794     FILE *f;
12795     char buf[MSG_SIZ];
12796     int result, i, t,tot=0;
12797
12798     if (strcmp(filename, "-") == 0) {
12799         return SaveGame(stdout, 0, NULL);
12800     } else {
12801         for(i=0; i<10; i++) { // upto 10 tries
12802              f = fopen(filename, append ? "a" : "w");
12803              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12804              if(f || errno != 13) break;
12805              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12806              tot += t;
12807         }
12808         if (f == NULL) {
12809             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12810             DisplayError(buf, errno);
12811             return FALSE;
12812         } else {
12813             safeStrCpy(buf, lastMsg, MSG_SIZ);
12814             DisplayMessage(_("Waiting for access to save file"), "");
12815             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12816             DisplayMessage(_("Saving game"), "");
12817             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12818             result = SaveGame(f, 0, NULL);
12819             DisplayMessage(buf, "");
12820             return result;
12821         }
12822     }
12823 }
12824
12825 char *
12826 SavePart (char *str)
12827 {
12828     static char buf[MSG_SIZ];
12829     char *p;
12830
12831     p = strchr(str, ' ');
12832     if (p == NULL) return str;
12833     strncpy(buf, str, p - str);
12834     buf[p - str] = NULLCHAR;
12835     return buf;
12836 }
12837
12838 #define PGN_MAX_LINE 75
12839
12840 #define PGN_SIDE_WHITE  0
12841 #define PGN_SIDE_BLACK  1
12842
12843 static int
12844 FindFirstMoveOutOfBook (int side)
12845 {
12846     int result = -1;
12847
12848     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12849         int index = backwardMostMove;
12850         int has_book_hit = 0;
12851
12852         if( (index % 2) != side ) {
12853             index++;
12854         }
12855
12856         while( index < forwardMostMove ) {
12857             /* Check to see if engine is in book */
12858             int depth = pvInfoList[index].depth;
12859             int score = pvInfoList[index].score;
12860             int in_book = 0;
12861
12862             if( depth <= 2 ) {
12863                 in_book = 1;
12864             }
12865             else if( score == 0 && depth == 63 ) {
12866                 in_book = 1; /* Zappa */
12867             }
12868             else if( score == 2 && depth == 99 ) {
12869                 in_book = 1; /* Abrok */
12870             }
12871
12872             has_book_hit += in_book;
12873
12874             if( ! in_book ) {
12875                 result = index;
12876
12877                 break;
12878             }
12879
12880             index += 2;
12881         }
12882     }
12883
12884     return result;
12885 }
12886
12887 void
12888 GetOutOfBookInfo (char * buf)
12889 {
12890     int oob[2];
12891     int i;
12892     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12893
12894     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12895     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12896
12897     *buf = '\0';
12898
12899     if( oob[0] >= 0 || oob[1] >= 0 ) {
12900         for( i=0; i<2; i++ ) {
12901             int idx = oob[i];
12902
12903             if( idx >= 0 ) {
12904                 if( i > 0 && oob[0] >= 0 ) {
12905                     strcat( buf, "   " );
12906                 }
12907
12908                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12909                 sprintf( buf+strlen(buf), "%s%.2f",
12910                     pvInfoList[idx].score >= 0 ? "+" : "",
12911                     pvInfoList[idx].score / 100.0 );
12912             }
12913         }
12914     }
12915 }
12916
12917 /* Save game in PGN style and close the file */
12918 int
12919 SaveGamePGN (FILE *f)
12920 {
12921     int i, offset, linelen, newblock;
12922 //    char *movetext;
12923     char numtext[32];
12924     int movelen, numlen, blank;
12925     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12926
12927     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12928
12929     PrintPGNTags(f, &gameInfo);
12930
12931     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12932
12933     if (backwardMostMove > 0 || startedFromSetupPosition) {
12934         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12935         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12936         fprintf(f, "\n{--------------\n");
12937         PrintPosition(f, backwardMostMove);
12938         fprintf(f, "--------------}\n");
12939         free(fen);
12940     }
12941     else {
12942         /* [AS] Out of book annotation */
12943         if( appData.saveOutOfBookInfo ) {
12944             char buf[64];
12945
12946             GetOutOfBookInfo( buf );
12947
12948             if( buf[0] != '\0' ) {
12949                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12950             }
12951         }
12952
12953         fprintf(f, "\n");
12954     }
12955
12956     i = backwardMostMove;
12957     linelen = 0;
12958     newblock = TRUE;
12959
12960     while (i < forwardMostMove) {
12961         /* Print comments preceding this move */
12962         if (commentList[i] != NULL) {
12963             if (linelen > 0) fprintf(f, "\n");
12964             fprintf(f, "%s", commentList[i]);
12965             linelen = 0;
12966             newblock = TRUE;
12967         }
12968
12969         /* Format move number */
12970         if ((i % 2) == 0)
12971           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12972         else
12973           if (newblock)
12974             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12975           else
12976             numtext[0] = NULLCHAR;
12977
12978         numlen = strlen(numtext);
12979         newblock = FALSE;
12980
12981         /* Print move number */
12982         blank = linelen > 0 && numlen > 0;
12983         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12984             fprintf(f, "\n");
12985             linelen = 0;
12986             blank = 0;
12987         }
12988         if (blank) {
12989             fprintf(f, " ");
12990             linelen++;
12991         }
12992         fprintf(f, "%s", numtext);
12993         linelen += numlen;
12994
12995         /* Get move */
12996         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12997         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12998
12999         /* Print move */
13000         blank = linelen > 0 && movelen > 0;
13001         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13002             fprintf(f, "\n");
13003             linelen = 0;
13004             blank = 0;
13005         }
13006         if (blank) {
13007             fprintf(f, " ");
13008             linelen++;
13009         }
13010         fprintf(f, "%s", move_buffer);
13011         linelen += movelen;
13012
13013         /* [AS] Add PV info if present */
13014         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13015             /* [HGM] add time */
13016             char buf[MSG_SIZ]; int seconds;
13017
13018             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13019
13020             if( seconds <= 0)
13021               buf[0] = 0;
13022             else
13023               if( seconds < 30 )
13024                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13025               else
13026                 {
13027                   seconds = (seconds + 4)/10; // round to full seconds
13028                   if( seconds < 60 )
13029                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13030                   else
13031                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13032                 }
13033
13034             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13035                       pvInfoList[i].score >= 0 ? "+" : "",
13036                       pvInfoList[i].score / 100.0,
13037                       pvInfoList[i].depth,
13038                       buf );
13039
13040             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13041
13042             /* Print score/depth */
13043             blank = linelen > 0 && movelen > 0;
13044             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13045                 fprintf(f, "\n");
13046                 linelen = 0;
13047                 blank = 0;
13048             }
13049             if (blank) {
13050                 fprintf(f, " ");
13051                 linelen++;
13052             }
13053             fprintf(f, "%s", move_buffer);
13054             linelen += movelen;
13055         }
13056
13057         i++;
13058     }
13059
13060     /* Start a new line */
13061     if (linelen > 0) fprintf(f, "\n");
13062
13063     /* Print comments after last move */
13064     if (commentList[i] != NULL) {
13065         fprintf(f, "%s\n", commentList[i]);
13066     }
13067
13068     /* Print result */
13069     if (gameInfo.resultDetails != NULL &&
13070         gameInfo.resultDetails[0] != NULLCHAR) {
13071         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
13072                 PGNResult(gameInfo.result));
13073     } else {
13074         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13075     }
13076
13077     fclose(f);
13078     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13079     return TRUE;
13080 }
13081
13082 /* Save game in old style and close the file */
13083 int
13084 SaveGameOldStyle (FILE *f)
13085 {
13086     int i, offset;
13087     time_t tm;
13088
13089     tm = time((time_t *) NULL);
13090
13091     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13092     PrintOpponents(f);
13093
13094     if (backwardMostMove > 0 || startedFromSetupPosition) {
13095         fprintf(f, "\n[--------------\n");
13096         PrintPosition(f, backwardMostMove);
13097         fprintf(f, "--------------]\n");
13098     } else {
13099         fprintf(f, "\n");
13100     }
13101
13102     i = backwardMostMove;
13103     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13104
13105     while (i < forwardMostMove) {
13106         if (commentList[i] != NULL) {
13107             fprintf(f, "[%s]\n", commentList[i]);
13108         }
13109
13110         if ((i % 2) == 1) {
13111             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13112             i++;
13113         } else {
13114             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13115             i++;
13116             if (commentList[i] != NULL) {
13117                 fprintf(f, "\n");
13118                 continue;
13119             }
13120             if (i >= forwardMostMove) {
13121                 fprintf(f, "\n");
13122                 break;
13123             }
13124             fprintf(f, "%s\n", parseList[i]);
13125             i++;
13126         }
13127     }
13128
13129     if (commentList[i] != NULL) {
13130         fprintf(f, "[%s]\n", commentList[i]);
13131     }
13132
13133     /* This isn't really the old style, but it's close enough */
13134     if (gameInfo.resultDetails != NULL &&
13135         gameInfo.resultDetails[0] != NULLCHAR) {
13136         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13137                 gameInfo.resultDetails);
13138     } else {
13139         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13140     }
13141
13142     fclose(f);
13143     return TRUE;
13144 }
13145
13146 /* Save the current game to open file f and close the file */
13147 int
13148 SaveGame (FILE *f, int dummy, char *dummy2)
13149 {
13150     if (gameMode == EditPosition) EditPositionDone(TRUE);
13151     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13152     if (appData.oldSaveStyle)
13153       return SaveGameOldStyle(f);
13154     else
13155       return SaveGamePGN(f);
13156 }
13157
13158 /* Save the current position to the given file */
13159 int
13160 SavePositionToFile (char *filename)
13161 {
13162     FILE *f;
13163     char buf[MSG_SIZ];
13164
13165     if (strcmp(filename, "-") == 0) {
13166         return SavePosition(stdout, 0, NULL);
13167     } else {
13168         f = fopen(filename, "a");
13169         if (f == NULL) {
13170             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13171             DisplayError(buf, errno);
13172             return FALSE;
13173         } else {
13174             safeStrCpy(buf, lastMsg, MSG_SIZ);
13175             DisplayMessage(_("Waiting for access to save file"), "");
13176             flock(fileno(f), LOCK_EX); // [HGM] lock
13177             DisplayMessage(_("Saving position"), "");
13178             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13179             SavePosition(f, 0, NULL);
13180             DisplayMessage(buf, "");
13181             return TRUE;
13182         }
13183     }
13184 }
13185
13186 /* Save the current position to the given open file and close the file */
13187 int
13188 SavePosition (FILE *f, int dummy, char *dummy2)
13189 {
13190     time_t tm;
13191     char *fen;
13192
13193     if (gameMode == EditPosition) EditPositionDone(TRUE);
13194     if (appData.oldSaveStyle) {
13195         tm = time((time_t *) NULL);
13196
13197         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13198         PrintOpponents(f);
13199         fprintf(f, "[--------------\n");
13200         PrintPosition(f, currentMove);
13201         fprintf(f, "--------------]\n");
13202     } else {
13203         fen = PositionToFEN(currentMove, NULL, 1);
13204         fprintf(f, "%s\n", fen);
13205         free(fen);
13206     }
13207     fclose(f);
13208     return TRUE;
13209 }
13210
13211 void
13212 ReloadCmailMsgEvent (int unregister)
13213 {
13214 #if !WIN32
13215     static char *inFilename = NULL;
13216     static char *outFilename;
13217     int i;
13218     struct stat inbuf, outbuf;
13219     int status;
13220
13221     /* Any registered moves are unregistered if unregister is set, */
13222     /* i.e. invoked by the signal handler */
13223     if (unregister) {
13224         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13225             cmailMoveRegistered[i] = FALSE;
13226             if (cmailCommentList[i] != NULL) {
13227                 free(cmailCommentList[i]);
13228                 cmailCommentList[i] = NULL;
13229             }
13230         }
13231         nCmailMovesRegistered = 0;
13232     }
13233
13234     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13235         cmailResult[i] = CMAIL_NOT_RESULT;
13236     }
13237     nCmailResults = 0;
13238
13239     if (inFilename == NULL) {
13240         /* Because the filenames are static they only get malloced once  */
13241         /* and they never get freed                                      */
13242         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13243         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13244
13245         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13246         sprintf(outFilename, "%s.out", appData.cmailGameName);
13247     }
13248
13249     status = stat(outFilename, &outbuf);
13250     if (status < 0) {
13251         cmailMailedMove = FALSE;
13252     } else {
13253         status = stat(inFilename, &inbuf);
13254         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13255     }
13256
13257     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13258        counts the games, notes how each one terminated, etc.
13259
13260        It would be nice to remove this kludge and instead gather all
13261        the information while building the game list.  (And to keep it
13262        in the game list nodes instead of having a bunch of fixed-size
13263        parallel arrays.)  Note this will require getting each game's
13264        termination from the PGN tags, as the game list builder does
13265        not process the game moves.  --mann
13266        */
13267     cmailMsgLoaded = TRUE;
13268     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13269
13270     /* Load first game in the file or popup game menu */
13271     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13272
13273 #endif /* !WIN32 */
13274     return;
13275 }
13276
13277 int
13278 RegisterMove ()
13279 {
13280     FILE *f;
13281     char string[MSG_SIZ];
13282
13283     if (   cmailMailedMove
13284         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13285         return TRUE;            /* Allow free viewing  */
13286     }
13287
13288     /* Unregister move to ensure that we don't leave RegisterMove        */
13289     /* with the move registered when the conditions for registering no   */
13290     /* longer hold                                                       */
13291     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13292         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13293         nCmailMovesRegistered --;
13294
13295         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13296           {
13297               free(cmailCommentList[lastLoadGameNumber - 1]);
13298               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13299           }
13300     }
13301
13302     if (cmailOldMove == -1) {
13303         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13304         return FALSE;
13305     }
13306
13307     if (currentMove > cmailOldMove + 1) {
13308         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13309         return FALSE;
13310     }
13311
13312     if (currentMove < cmailOldMove) {
13313         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13314         return FALSE;
13315     }
13316
13317     if (forwardMostMove > currentMove) {
13318         /* Silently truncate extra moves */
13319         TruncateGame();
13320     }
13321
13322     if (   (currentMove == cmailOldMove + 1)
13323         || (   (currentMove == cmailOldMove)
13324             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13325                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13326         if (gameInfo.result != GameUnfinished) {
13327             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13328         }
13329
13330         if (commentList[currentMove] != NULL) {
13331             cmailCommentList[lastLoadGameNumber - 1]
13332               = StrSave(commentList[currentMove]);
13333         }
13334         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13335
13336         if (appData.debugMode)
13337           fprintf(debugFP, "Saving %s for game %d\n",
13338                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13339
13340         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13341
13342         f = fopen(string, "w");
13343         if (appData.oldSaveStyle) {
13344             SaveGameOldStyle(f); /* also closes the file */
13345
13346             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13347             f = fopen(string, "w");
13348             SavePosition(f, 0, NULL); /* also closes the file */
13349         } else {
13350             fprintf(f, "{--------------\n");
13351             PrintPosition(f, currentMove);
13352             fprintf(f, "--------------}\n\n");
13353
13354             SaveGame(f, 0, NULL); /* also closes the file*/
13355         }
13356
13357         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13358         nCmailMovesRegistered ++;
13359     } else if (nCmailGames == 1) {
13360         DisplayError(_("You have not made a move yet"), 0);
13361         return FALSE;
13362     }
13363
13364     return TRUE;
13365 }
13366
13367 void
13368 MailMoveEvent ()
13369 {
13370 #if !WIN32
13371     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13372     FILE *commandOutput;
13373     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13374     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13375     int nBuffers;
13376     int i;
13377     int archived;
13378     char *arcDir;
13379
13380     if (! cmailMsgLoaded) {
13381         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13382         return;
13383     }
13384
13385     if (nCmailGames == nCmailResults) {
13386         DisplayError(_("No unfinished games"), 0);
13387         return;
13388     }
13389
13390 #if CMAIL_PROHIBIT_REMAIL
13391     if (cmailMailedMove) {
13392       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);
13393         DisplayError(msg, 0);
13394         return;
13395     }
13396 #endif
13397
13398     if (! (cmailMailedMove || RegisterMove())) return;
13399
13400     if (   cmailMailedMove
13401         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13402       snprintf(string, MSG_SIZ, partCommandString,
13403                appData.debugMode ? " -v" : "", appData.cmailGameName);
13404         commandOutput = popen(string, "r");
13405
13406         if (commandOutput == NULL) {
13407             DisplayError(_("Failed to invoke cmail"), 0);
13408         } else {
13409             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13410                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13411             }
13412             if (nBuffers > 1) {
13413                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13414                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13415                 nBytes = MSG_SIZ - 1;
13416             } else {
13417                 (void) memcpy(msg, buffer, nBytes);
13418             }
13419             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13420
13421             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13422                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13423
13424                 archived = TRUE;
13425                 for (i = 0; i < nCmailGames; i ++) {
13426                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13427                         archived = FALSE;
13428                     }
13429                 }
13430                 if (   archived
13431                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13432                         != NULL)) {
13433                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13434                            arcDir,
13435                            appData.cmailGameName,
13436                            gameInfo.date);
13437                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13438                     cmailMsgLoaded = FALSE;
13439                 }
13440             }
13441
13442             DisplayInformation(msg);
13443             pclose(commandOutput);
13444         }
13445     } else {
13446         if ((*cmailMsg) != '\0') {
13447             DisplayInformation(cmailMsg);
13448         }
13449     }
13450
13451     return;
13452 #endif /* !WIN32 */
13453 }
13454
13455 char *
13456 CmailMsg ()
13457 {
13458 #if WIN32
13459     return NULL;
13460 #else
13461     int  prependComma = 0;
13462     char number[5];
13463     char string[MSG_SIZ];       /* Space for game-list */
13464     int  i;
13465
13466     if (!cmailMsgLoaded) return "";
13467
13468     if (cmailMailedMove) {
13469       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13470     } else {
13471         /* Create a list of games left */
13472       snprintf(string, MSG_SIZ, "[");
13473         for (i = 0; i < nCmailGames; i ++) {
13474             if (! (   cmailMoveRegistered[i]
13475                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13476                 if (prependComma) {
13477                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13478                 } else {
13479                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13480                     prependComma = 1;
13481                 }
13482
13483                 strcat(string, number);
13484             }
13485         }
13486         strcat(string, "]");
13487
13488         if (nCmailMovesRegistered + nCmailResults == 0) {
13489             switch (nCmailGames) {
13490               case 1:
13491                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13492                 break;
13493
13494               case 2:
13495                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13496                 break;
13497
13498               default:
13499                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13500                          nCmailGames);
13501                 break;
13502             }
13503         } else {
13504             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13505               case 1:
13506                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13507                          string);
13508                 break;
13509
13510               case 0:
13511                 if (nCmailResults == nCmailGames) {
13512                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13513                 } else {
13514                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13515                 }
13516                 break;
13517
13518               default:
13519                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13520                          string);
13521             }
13522         }
13523     }
13524     return cmailMsg;
13525 #endif /* WIN32 */
13526 }
13527
13528 void
13529 ResetGameEvent ()
13530 {
13531     if (gameMode == Training)
13532       SetTrainingModeOff();
13533
13534     Reset(TRUE, TRUE);
13535     cmailMsgLoaded = FALSE;
13536     if (appData.icsActive) {
13537       SendToICS(ics_prefix);
13538       SendToICS("refresh\n");
13539     }
13540 }
13541
13542 void
13543 ExitEvent (int status)
13544 {
13545     exiting++;
13546     if (exiting > 2) {
13547       /* Give up on clean exit */
13548       exit(status);
13549     }
13550     if (exiting > 1) {
13551       /* Keep trying for clean exit */
13552       return;
13553     }
13554
13555     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13556
13557     if (telnetISR != NULL) {
13558       RemoveInputSource(telnetISR);
13559     }
13560     if (icsPR != NoProc) {
13561       DestroyChildProcess(icsPR, TRUE);
13562     }
13563
13564     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13565     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13566
13567     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13568     /* make sure this other one finishes before killing it!                  */
13569     if(endingGame) { int count = 0;
13570         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13571         while(endingGame && count++ < 10) DoSleep(1);
13572         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13573     }
13574
13575     /* Kill off chess programs */
13576     if (first.pr != NoProc) {
13577         ExitAnalyzeMode();
13578
13579         DoSleep( appData.delayBeforeQuit );
13580         SendToProgram("quit\n", &first);
13581         DoSleep( appData.delayAfterQuit );
13582         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13583     }
13584     if (second.pr != NoProc) {
13585         DoSleep( appData.delayBeforeQuit );
13586         SendToProgram("quit\n", &second);
13587         DoSleep( appData.delayAfterQuit );
13588         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13589     }
13590     if (first.isr != NULL) {
13591         RemoveInputSource(first.isr);
13592     }
13593     if (second.isr != NULL) {
13594         RemoveInputSource(second.isr);
13595     }
13596
13597     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13598     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13599
13600     ShutDownFrontEnd();
13601     exit(status);
13602 }
13603
13604 void
13605 PauseEngine (ChessProgramState *cps)
13606 {
13607     SendToProgram("pause\n", cps);
13608     cps->pause = 2;
13609 }
13610
13611 void
13612 UnPauseEngine (ChessProgramState *cps)
13613 {
13614     SendToProgram("resume\n", cps);
13615     cps->pause = 1;
13616 }
13617
13618 void
13619 PauseEvent ()
13620 {
13621     if (appData.debugMode)
13622         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13623     if (pausing) {
13624         pausing = FALSE;
13625         ModeHighlight();
13626         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13627             StartClocks();
13628             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13629                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13630                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13631             }
13632             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13633             HandleMachineMove(stashedInputMove, stalledEngine);
13634             stalledEngine = NULL;
13635             return;
13636         }
13637         if (gameMode == MachinePlaysWhite ||
13638             gameMode == TwoMachinesPlay   ||
13639             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13640             if(first.pause)  UnPauseEngine(&first);
13641             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13642             if(second.pause) UnPauseEngine(&second);
13643             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13644             StartClocks();
13645         } else {
13646             DisplayBothClocks();
13647         }
13648         if (gameMode == PlayFromGameFile) {
13649             if (appData.timeDelay >= 0)
13650                 AutoPlayGameLoop();
13651         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13652             Reset(FALSE, TRUE);
13653             SendToICS(ics_prefix);
13654             SendToICS("refresh\n");
13655         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13656             ForwardInner(forwardMostMove);
13657         }
13658         pauseExamInvalid = FALSE;
13659     } else {
13660         switch (gameMode) {
13661           default:
13662             return;
13663           case IcsExamining:
13664             pauseExamForwardMostMove = forwardMostMove;
13665             pauseExamInvalid = FALSE;
13666             /* fall through */
13667           case IcsObserving:
13668           case IcsPlayingWhite:
13669           case IcsPlayingBlack:
13670             pausing = TRUE;
13671             ModeHighlight();
13672             return;
13673           case PlayFromGameFile:
13674             (void) StopLoadGameTimer();
13675             pausing = TRUE;
13676             ModeHighlight();
13677             break;
13678           case BeginningOfGame:
13679             if (appData.icsActive) return;
13680             /* else fall through */
13681           case MachinePlaysWhite:
13682           case MachinePlaysBlack:
13683           case TwoMachinesPlay:
13684             if (forwardMostMove == 0)
13685               return;           /* don't pause if no one has moved */
13686             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13687                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13688                 if(onMove->pause) {           // thinking engine can be paused
13689                     PauseEngine(onMove);      // do it
13690                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13691                         PauseEngine(onMove->other);
13692                     else
13693                         SendToProgram("easy\n", onMove->other);
13694                     StopClocks();
13695                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13696             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13697                 if(first.pause) {
13698                     PauseEngine(&first);
13699                     StopClocks();
13700                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13701             } else { // human on move, pause pondering by either method
13702                 if(first.pause)
13703                     PauseEngine(&first);
13704                 else if(appData.ponderNextMove)
13705                     SendToProgram("easy\n", &first);
13706                 StopClocks();
13707             }
13708             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13709           case AnalyzeMode:
13710             pausing = TRUE;
13711             ModeHighlight();
13712             break;
13713         }
13714     }
13715 }
13716
13717 void
13718 EditCommentEvent ()
13719 {
13720     char title[MSG_SIZ];
13721
13722     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13723       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13724     } else {
13725       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13726                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13727                parseList[currentMove - 1]);
13728     }
13729
13730     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13731 }
13732
13733
13734 void
13735 EditTagsEvent ()
13736 {
13737     char *tags = PGNTags(&gameInfo);
13738     bookUp = FALSE;
13739     EditTagsPopUp(tags, NULL);
13740     free(tags);
13741 }
13742
13743 void
13744 ToggleSecond ()
13745 {
13746   if(second.analyzing) {
13747     SendToProgram("exit\n", &second);
13748     second.analyzing = FALSE;
13749   } else {
13750     if (second.pr == NoProc) StartChessProgram(&second);
13751     InitChessProgram(&second, FALSE);
13752     FeedMovesToProgram(&second, currentMove);
13753
13754     SendToProgram("analyze\n", &second);
13755     second.analyzing = TRUE;
13756   }
13757 }
13758
13759 /* Toggle ShowThinking */
13760 void
13761 ToggleShowThinking()
13762 {
13763   appData.showThinking = !appData.showThinking;
13764   ShowThinkingEvent();
13765 }
13766
13767 int
13768 AnalyzeModeEvent ()
13769 {
13770     char buf[MSG_SIZ];
13771
13772     if (!first.analysisSupport) {
13773       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13774       DisplayError(buf, 0);
13775       return 0;
13776     }
13777     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13778     if (appData.icsActive) {
13779         if (gameMode != IcsObserving) {
13780           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13781             DisplayError(buf, 0);
13782             /* secure check */
13783             if (appData.icsEngineAnalyze) {
13784                 if (appData.debugMode)
13785                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13786                 ExitAnalyzeMode();
13787                 ModeHighlight();
13788             }
13789             return 0;
13790         }
13791         /* if enable, user wants to disable icsEngineAnalyze */
13792         if (appData.icsEngineAnalyze) {
13793                 ExitAnalyzeMode();
13794                 ModeHighlight();
13795                 return 0;
13796         }
13797         appData.icsEngineAnalyze = TRUE;
13798         if (appData.debugMode)
13799             fprintf(debugFP, "ICS engine analyze starting... \n");
13800     }
13801
13802     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13803     if (appData.noChessProgram || gameMode == AnalyzeMode)
13804       return 0;
13805
13806     if (gameMode != AnalyzeFile) {
13807         if (!appData.icsEngineAnalyze) {
13808                EditGameEvent();
13809                if (gameMode != EditGame) return 0;
13810         }
13811         if (!appData.showThinking) ToggleShowThinking();
13812         ResurrectChessProgram();
13813         SendToProgram("analyze\n", &first);
13814         first.analyzing = TRUE;
13815         /*first.maybeThinking = TRUE;*/
13816         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13817         EngineOutputPopUp();
13818     }
13819     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13820     pausing = FALSE;
13821     ModeHighlight();
13822     SetGameInfo();
13823
13824     StartAnalysisClock();
13825     GetTimeMark(&lastNodeCountTime);
13826     lastNodeCount = 0;
13827     return 1;
13828 }
13829
13830 void
13831 AnalyzeFileEvent ()
13832 {
13833     if (appData.noChessProgram || gameMode == AnalyzeFile)
13834       return;
13835
13836     if (!first.analysisSupport) {
13837       char buf[MSG_SIZ];
13838       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13839       DisplayError(buf, 0);
13840       return;
13841     }
13842
13843     if (gameMode != AnalyzeMode) {
13844         keepInfo = 1; // mere annotating should not alter PGN tags
13845         EditGameEvent();
13846         keepInfo = 0;
13847         if (gameMode != EditGame) return;
13848         if (!appData.showThinking) ToggleShowThinking();
13849         ResurrectChessProgram();
13850         SendToProgram("analyze\n", &first);
13851         first.analyzing = TRUE;
13852         /*first.maybeThinking = TRUE;*/
13853         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13854         EngineOutputPopUp();
13855     }
13856     gameMode = AnalyzeFile;
13857     pausing = FALSE;
13858     ModeHighlight();
13859
13860     StartAnalysisClock();
13861     GetTimeMark(&lastNodeCountTime);
13862     lastNodeCount = 0;
13863     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13864     AnalysisPeriodicEvent(1);
13865 }
13866
13867 void
13868 MachineWhiteEvent ()
13869 {
13870     char buf[MSG_SIZ];
13871     char *bookHit = NULL;
13872
13873     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13874       return;
13875
13876
13877     if (gameMode == PlayFromGameFile ||
13878         gameMode == TwoMachinesPlay  ||
13879         gameMode == Training         ||
13880         gameMode == AnalyzeMode      ||
13881         gameMode == EndOfGame)
13882         EditGameEvent();
13883
13884     if (gameMode == EditPosition)
13885         EditPositionDone(TRUE);
13886
13887     if (!WhiteOnMove(currentMove)) {
13888         DisplayError(_("It is not White's turn"), 0);
13889         return;
13890     }
13891
13892     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13893       ExitAnalyzeMode();
13894
13895     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13896         gameMode == AnalyzeFile)
13897         TruncateGame();
13898
13899     ResurrectChessProgram();    /* in case it isn't running */
13900     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13901         gameMode = MachinePlaysWhite;
13902         ResetClocks();
13903     } else
13904     gameMode = MachinePlaysWhite;
13905     pausing = FALSE;
13906     ModeHighlight();
13907     SetGameInfo();
13908     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13909     DisplayTitle(buf);
13910     if (first.sendName) {
13911       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13912       SendToProgram(buf, &first);
13913     }
13914     if (first.sendTime) {
13915       if (first.useColors) {
13916         SendToProgram("black\n", &first); /*gnu kludge*/
13917       }
13918       SendTimeRemaining(&first, TRUE);
13919     }
13920     if (first.useColors) {
13921       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13922     }
13923     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13924     SetMachineThinkingEnables();
13925     first.maybeThinking = TRUE;
13926     StartClocks();
13927     firstMove = FALSE;
13928
13929     if (appData.autoFlipView && !flipView) {
13930       flipView = !flipView;
13931       DrawPosition(FALSE, NULL);
13932       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13933     }
13934
13935     if(bookHit) { // [HGM] book: simulate book reply
13936         static char bookMove[MSG_SIZ]; // a bit generous?
13937
13938         programStats.nodes = programStats.depth = programStats.time =
13939         programStats.score = programStats.got_only_move = 0;
13940         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13941
13942         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13943         strcat(bookMove, bookHit);
13944         HandleMachineMove(bookMove, &first);
13945     }
13946 }
13947
13948 void
13949 MachineBlackEvent ()
13950 {
13951   char buf[MSG_SIZ];
13952   char *bookHit = NULL;
13953
13954     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13955         return;
13956
13957
13958     if (gameMode == PlayFromGameFile ||
13959         gameMode == TwoMachinesPlay  ||
13960         gameMode == Training         ||
13961         gameMode == AnalyzeMode      ||
13962         gameMode == EndOfGame)
13963         EditGameEvent();
13964
13965     if (gameMode == EditPosition)
13966         EditPositionDone(TRUE);
13967
13968     if (WhiteOnMove(currentMove)) {
13969         DisplayError(_("It is not Black's turn"), 0);
13970         return;
13971     }
13972
13973     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13974       ExitAnalyzeMode();
13975
13976     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13977         gameMode == AnalyzeFile)
13978         TruncateGame();
13979
13980     ResurrectChessProgram();    /* in case it isn't running */
13981     gameMode = MachinePlaysBlack;
13982     pausing = FALSE;
13983     ModeHighlight();
13984     SetGameInfo();
13985     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13986     DisplayTitle(buf);
13987     if (first.sendName) {
13988       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13989       SendToProgram(buf, &first);
13990     }
13991     if (first.sendTime) {
13992       if (first.useColors) {
13993         SendToProgram("white\n", &first); /*gnu kludge*/
13994       }
13995       SendTimeRemaining(&first, FALSE);
13996     }
13997     if (first.useColors) {
13998       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13999     }
14000     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14001     SetMachineThinkingEnables();
14002     first.maybeThinking = TRUE;
14003     StartClocks();
14004
14005     if (appData.autoFlipView && flipView) {
14006       flipView = !flipView;
14007       DrawPosition(FALSE, NULL);
14008       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14009     }
14010     if(bookHit) { // [HGM] book: simulate book reply
14011         static char bookMove[MSG_SIZ]; // a bit generous?
14012
14013         programStats.nodes = programStats.depth = programStats.time =
14014         programStats.score = programStats.got_only_move = 0;
14015         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14016
14017         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14018         strcat(bookMove, bookHit);
14019         HandleMachineMove(bookMove, &first);
14020     }
14021 }
14022
14023
14024 void
14025 DisplayTwoMachinesTitle ()
14026 {
14027     char buf[MSG_SIZ];
14028     if (appData.matchGames > 0) {
14029         if(appData.tourneyFile[0]) {
14030           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14031                    gameInfo.white, _("vs."), gameInfo.black,
14032                    nextGame+1, appData.matchGames+1,
14033                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14034         } else
14035         if (first.twoMachinesColor[0] == 'w') {
14036           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14037                    gameInfo.white, _("vs."),  gameInfo.black,
14038                    first.matchWins, second.matchWins,
14039                    matchGame - 1 - (first.matchWins + second.matchWins));
14040         } else {
14041           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14042                    gameInfo.white, _("vs."), gameInfo.black,
14043                    second.matchWins, first.matchWins,
14044                    matchGame - 1 - (first.matchWins + second.matchWins));
14045         }
14046     } else {
14047       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14048     }
14049     DisplayTitle(buf);
14050 }
14051
14052 void
14053 SettingsMenuIfReady ()
14054 {
14055   if (second.lastPing != second.lastPong) {
14056     DisplayMessage("", _("Waiting for second chess program"));
14057     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14058     return;
14059   }
14060   ThawUI();
14061   DisplayMessage("", "");
14062   SettingsPopUp(&second);
14063 }
14064
14065 int
14066 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14067 {
14068     char buf[MSG_SIZ];
14069     if (cps->pr == NoProc) {
14070         StartChessProgram(cps);
14071         if (cps->protocolVersion == 1) {
14072           retry();
14073           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14074         } else {
14075           /* kludge: allow timeout for initial "feature" command */
14076           if(retry != TwoMachinesEventIfReady) FreezeUI();
14077           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14078           DisplayMessage("", buf);
14079           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14080         }
14081         return 1;
14082     }
14083     return 0;
14084 }
14085
14086 void
14087 TwoMachinesEvent P((void))
14088 {
14089     int i;
14090     char buf[MSG_SIZ];
14091     ChessProgramState *onmove;
14092     char *bookHit = NULL;
14093     static int stalling = 0;
14094     TimeMark now;
14095     long wait;
14096
14097     if (appData.noChessProgram) return;
14098
14099     switch (gameMode) {
14100       case TwoMachinesPlay:
14101         return;
14102       case MachinePlaysWhite:
14103       case MachinePlaysBlack:
14104         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14105             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14106             return;
14107         }
14108         /* fall through */
14109       case BeginningOfGame:
14110       case PlayFromGameFile:
14111       case EndOfGame:
14112         EditGameEvent();
14113         if (gameMode != EditGame) return;
14114         break;
14115       case EditPosition:
14116         EditPositionDone(TRUE);
14117         break;
14118       case AnalyzeMode:
14119       case AnalyzeFile:
14120         ExitAnalyzeMode();
14121         break;
14122       case EditGame:
14123       default:
14124         break;
14125     }
14126
14127 //    forwardMostMove = currentMove;
14128     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14129     startingEngine = TRUE;
14130
14131     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14132
14133     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14134     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14135       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14136       return;
14137     }
14138     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14139
14140     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14141         startingEngine = FALSE;
14142         DisplayError("second engine does not play this", 0);
14143         return;
14144     }
14145
14146     if(!stalling) {
14147       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14148       SendToProgram("force\n", &second);
14149       stalling = 1;
14150       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14151       return;
14152     }
14153     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14154     if(appData.matchPause>10000 || appData.matchPause<10)
14155                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14156     wait = SubtractTimeMarks(&now, &pauseStart);
14157     if(wait < appData.matchPause) {
14158         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14159         return;
14160     }
14161     // we are now committed to starting the game
14162     stalling = 0;
14163     DisplayMessage("", "");
14164     if (startedFromSetupPosition) {
14165         SendBoard(&second, backwardMostMove);
14166     if (appData.debugMode) {
14167         fprintf(debugFP, "Two Machines\n");
14168     }
14169     }
14170     for (i = backwardMostMove; i < forwardMostMove; i++) {
14171         SendMoveToProgram(i, &second);
14172     }
14173
14174     gameMode = TwoMachinesPlay;
14175     pausing = startingEngine = FALSE;
14176     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14177     SetGameInfo();
14178     DisplayTwoMachinesTitle();
14179     firstMove = TRUE;
14180     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14181         onmove = &first;
14182     } else {
14183         onmove = &second;
14184     }
14185     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14186     SendToProgram(first.computerString, &first);
14187     if (first.sendName) {
14188       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14189       SendToProgram(buf, &first);
14190     }
14191     SendToProgram(second.computerString, &second);
14192     if (second.sendName) {
14193       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14194       SendToProgram(buf, &second);
14195     }
14196
14197     ResetClocks();
14198     if (!first.sendTime || !second.sendTime) {
14199         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14200         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14201     }
14202     if (onmove->sendTime) {
14203       if (onmove->useColors) {
14204         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14205       }
14206       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14207     }
14208     if (onmove->useColors) {
14209       SendToProgram(onmove->twoMachinesColor, onmove);
14210     }
14211     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14212 //    SendToProgram("go\n", onmove);
14213     onmove->maybeThinking = TRUE;
14214     SetMachineThinkingEnables();
14215
14216     StartClocks();
14217
14218     if(bookHit) { // [HGM] book: simulate book reply
14219         static char bookMove[MSG_SIZ]; // a bit generous?
14220
14221         programStats.nodes = programStats.depth = programStats.time =
14222         programStats.score = programStats.got_only_move = 0;
14223         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14224
14225         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14226         strcat(bookMove, bookHit);
14227         savedMessage = bookMove; // args for deferred call
14228         savedState = onmove;
14229         ScheduleDelayedEvent(DeferredBookMove, 1);
14230     }
14231 }
14232
14233 void
14234 TrainingEvent ()
14235 {
14236     if (gameMode == Training) {
14237       SetTrainingModeOff();
14238       gameMode = PlayFromGameFile;
14239       DisplayMessage("", _("Training mode off"));
14240     } else {
14241       gameMode = Training;
14242       animateTraining = appData.animate;
14243
14244       /* make sure we are not already at the end of the game */
14245       if (currentMove < forwardMostMove) {
14246         SetTrainingModeOn();
14247         DisplayMessage("", _("Training mode on"));
14248       } else {
14249         gameMode = PlayFromGameFile;
14250         DisplayError(_("Already at end of game"), 0);
14251       }
14252     }
14253     ModeHighlight();
14254 }
14255
14256 void
14257 IcsClientEvent ()
14258 {
14259     if (!appData.icsActive) return;
14260     switch (gameMode) {
14261       case IcsPlayingWhite:
14262       case IcsPlayingBlack:
14263       case IcsObserving:
14264       case IcsIdle:
14265       case BeginningOfGame:
14266       case IcsExamining:
14267         return;
14268
14269       case EditGame:
14270         break;
14271
14272       case EditPosition:
14273         EditPositionDone(TRUE);
14274         break;
14275
14276       case AnalyzeMode:
14277       case AnalyzeFile:
14278         ExitAnalyzeMode();
14279         break;
14280
14281       default:
14282         EditGameEvent();
14283         break;
14284     }
14285
14286     gameMode = IcsIdle;
14287     ModeHighlight();
14288     return;
14289 }
14290
14291 void
14292 EditGameEvent ()
14293 {
14294     int i;
14295
14296     switch (gameMode) {
14297       case Training:
14298         SetTrainingModeOff();
14299         break;
14300       case MachinePlaysWhite:
14301       case MachinePlaysBlack:
14302       case BeginningOfGame:
14303         SendToProgram("force\n", &first);
14304         SetUserThinkingEnables();
14305         break;
14306       case PlayFromGameFile:
14307         (void) StopLoadGameTimer();
14308         if (gameFileFP != NULL) {
14309             gameFileFP = NULL;
14310         }
14311         break;
14312       case EditPosition:
14313         EditPositionDone(TRUE);
14314         break;
14315       case AnalyzeMode:
14316       case AnalyzeFile:
14317         ExitAnalyzeMode();
14318         SendToProgram("force\n", &first);
14319         break;
14320       case TwoMachinesPlay:
14321         GameEnds(EndOfFile, NULL, GE_PLAYER);
14322         ResurrectChessProgram();
14323         SetUserThinkingEnables();
14324         break;
14325       case EndOfGame:
14326         ResurrectChessProgram();
14327         break;
14328       case IcsPlayingBlack:
14329       case IcsPlayingWhite:
14330         DisplayError(_("Warning: You are still playing a game"), 0);
14331         break;
14332       case IcsObserving:
14333         DisplayError(_("Warning: You are still observing a game"), 0);
14334         break;
14335       case IcsExamining:
14336         DisplayError(_("Warning: You are still examining a game"), 0);
14337         break;
14338       case IcsIdle:
14339         break;
14340       case EditGame:
14341       default:
14342         return;
14343     }
14344
14345     pausing = FALSE;
14346     StopClocks();
14347     first.offeredDraw = second.offeredDraw = 0;
14348
14349     if (gameMode == PlayFromGameFile) {
14350         whiteTimeRemaining = timeRemaining[0][currentMove];
14351         blackTimeRemaining = timeRemaining[1][currentMove];
14352         DisplayTitle("");
14353     }
14354
14355     if (gameMode == MachinePlaysWhite ||
14356         gameMode == MachinePlaysBlack ||
14357         gameMode == TwoMachinesPlay ||
14358         gameMode == EndOfGame) {
14359         i = forwardMostMove;
14360         while (i > currentMove) {
14361             SendToProgram("undo\n", &first);
14362             i--;
14363         }
14364         if(!adjustedClock) {
14365         whiteTimeRemaining = timeRemaining[0][currentMove];
14366         blackTimeRemaining = timeRemaining[1][currentMove];
14367         DisplayBothClocks();
14368         }
14369         if (whiteFlag || blackFlag) {
14370             whiteFlag = blackFlag = 0;
14371         }
14372         DisplayTitle("");
14373     }
14374
14375     gameMode = EditGame;
14376     ModeHighlight();
14377     SetGameInfo();
14378 }
14379
14380
14381 void
14382 EditPositionEvent ()
14383 {
14384     if (gameMode == EditPosition) {
14385         EditGameEvent();
14386         return;
14387     }
14388
14389     EditGameEvent();
14390     if (gameMode != EditGame) return;
14391
14392     gameMode = EditPosition;
14393     ModeHighlight();
14394     SetGameInfo();
14395     if (currentMove > 0)
14396       CopyBoard(boards[0], boards[currentMove]);
14397
14398     blackPlaysFirst = !WhiteOnMove(currentMove);
14399     ResetClocks();
14400     currentMove = forwardMostMove = backwardMostMove = 0;
14401     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14402     DisplayMove(-1);
14403     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14404 }
14405
14406 void
14407 ExitAnalyzeMode ()
14408 {
14409     /* [DM] icsEngineAnalyze - possible call from other functions */
14410     if (appData.icsEngineAnalyze) {
14411         appData.icsEngineAnalyze = FALSE;
14412
14413         DisplayMessage("",_("Close ICS engine analyze..."));
14414     }
14415     if (first.analysisSupport && first.analyzing) {
14416       SendToBoth("exit\n");
14417       first.analyzing = second.analyzing = FALSE;
14418     }
14419     thinkOutput[0] = NULLCHAR;
14420 }
14421
14422 void
14423 EditPositionDone (Boolean fakeRights)
14424 {
14425     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14426
14427     startedFromSetupPosition = TRUE;
14428     InitChessProgram(&first, FALSE);
14429     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14430       boards[0][EP_STATUS] = EP_NONE;
14431       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14432       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14433         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14434         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14435       } else boards[0][CASTLING][2] = NoRights;
14436       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14437         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14438         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14439       } else boards[0][CASTLING][5] = NoRights;
14440       if(gameInfo.variant == VariantSChess) {
14441         int i;
14442         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14443           boards[0][VIRGIN][i] = 0;
14444           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14445           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14446         }
14447       }
14448     }
14449     SendToProgram("force\n", &first);
14450     if (blackPlaysFirst) {
14451         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14452         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14453         currentMove = forwardMostMove = backwardMostMove = 1;
14454         CopyBoard(boards[1], boards[0]);
14455     } else {
14456         currentMove = forwardMostMove = backwardMostMove = 0;
14457     }
14458     SendBoard(&first, forwardMostMove);
14459     if (appData.debugMode) {
14460         fprintf(debugFP, "EditPosDone\n");
14461     }
14462     DisplayTitle("");
14463     DisplayMessage("", "");
14464     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14465     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14466     gameMode = EditGame;
14467     ModeHighlight();
14468     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14469     ClearHighlights(); /* [AS] */
14470 }
14471
14472 /* Pause for `ms' milliseconds */
14473 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14474 void
14475 TimeDelay (long ms)
14476 {
14477     TimeMark m1, m2;
14478
14479     GetTimeMark(&m1);
14480     do {
14481         GetTimeMark(&m2);
14482     } while (SubtractTimeMarks(&m2, &m1) < ms);
14483 }
14484
14485 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14486 void
14487 SendMultiLineToICS (char *buf)
14488 {
14489     char temp[MSG_SIZ+1], *p;
14490     int len;
14491
14492     len = strlen(buf);
14493     if (len > MSG_SIZ)
14494       len = MSG_SIZ;
14495
14496     strncpy(temp, buf, len);
14497     temp[len] = 0;
14498
14499     p = temp;
14500     while (*p) {
14501         if (*p == '\n' || *p == '\r')
14502           *p = ' ';
14503         ++p;
14504     }
14505
14506     strcat(temp, "\n");
14507     SendToICS(temp);
14508     SendToPlayer(temp, strlen(temp));
14509 }
14510
14511 void
14512 SetWhiteToPlayEvent ()
14513 {
14514     if (gameMode == EditPosition) {
14515         blackPlaysFirst = FALSE;
14516         DisplayBothClocks();    /* works because currentMove is 0 */
14517     } else if (gameMode == IcsExamining) {
14518         SendToICS(ics_prefix);
14519         SendToICS("tomove white\n");
14520     }
14521 }
14522
14523 void
14524 SetBlackToPlayEvent ()
14525 {
14526     if (gameMode == EditPosition) {
14527         blackPlaysFirst = TRUE;
14528         currentMove = 1;        /* kludge */
14529         DisplayBothClocks();
14530         currentMove = 0;
14531     } else if (gameMode == IcsExamining) {
14532         SendToICS(ics_prefix);
14533         SendToICS("tomove black\n");
14534     }
14535 }
14536
14537 void
14538 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14539 {
14540     char buf[MSG_SIZ];
14541     ChessSquare piece = boards[0][y][x];
14542
14543     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14544
14545     switch (selection) {
14546       case ClearBoard:
14547         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14548             SendToICS(ics_prefix);
14549             SendToICS("bsetup clear\n");
14550         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14551             SendToICS(ics_prefix);
14552             SendToICS("clearboard\n");
14553         } else {
14554             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14555                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14556                 for (y = 0; y < BOARD_HEIGHT; y++) {
14557                     if (gameMode == IcsExamining) {
14558                         if (boards[currentMove][y][x] != EmptySquare) {
14559                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14560                                     AAA + x, ONE + y);
14561                             SendToICS(buf);
14562                         }
14563                     } else {
14564                         boards[0][y][x] = p;
14565                     }
14566                 }
14567             }
14568         }
14569         if (gameMode == EditPosition) {
14570             DrawPosition(FALSE, boards[0]);
14571         }
14572         break;
14573
14574       case WhitePlay:
14575         SetWhiteToPlayEvent();
14576         break;
14577
14578       case BlackPlay:
14579         SetBlackToPlayEvent();
14580         break;
14581
14582       case EmptySquare:
14583         if (gameMode == IcsExamining) {
14584             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14585             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14586             SendToICS(buf);
14587         } else {
14588             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14589                 if(x == BOARD_LEFT-2) {
14590                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14591                     boards[0][y][1] = 0;
14592                 } else
14593                 if(x == BOARD_RGHT+1) {
14594                     if(y >= gameInfo.holdingsSize) break;
14595                     boards[0][y][BOARD_WIDTH-2] = 0;
14596                 } else break;
14597             }
14598             boards[0][y][x] = EmptySquare;
14599             DrawPosition(FALSE, boards[0]);
14600         }
14601         break;
14602
14603       case PromotePiece:
14604         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14605            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14606             selection = (ChessSquare) (PROMOTED piece);
14607         } else if(piece == EmptySquare) selection = WhiteSilver;
14608         else selection = (ChessSquare)((int)piece - 1);
14609         goto defaultlabel;
14610
14611       case DemotePiece:
14612         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14613            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14614             selection = (ChessSquare) (DEMOTED piece);
14615         } else if(piece == EmptySquare) selection = BlackSilver;
14616         else selection = (ChessSquare)((int)piece + 1);
14617         goto defaultlabel;
14618
14619       case WhiteQueen:
14620       case BlackQueen:
14621         if(gameInfo.variant == VariantShatranj ||
14622            gameInfo.variant == VariantXiangqi  ||
14623            gameInfo.variant == VariantCourier  ||
14624            gameInfo.variant == VariantASEAN    ||
14625            gameInfo.variant == VariantMakruk     )
14626             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14627         goto defaultlabel;
14628
14629       case WhiteKing:
14630       case BlackKing:
14631         if(gameInfo.variant == VariantXiangqi)
14632             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14633         if(gameInfo.variant == VariantKnightmate)
14634             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14635       default:
14636         defaultlabel:
14637         if (gameMode == IcsExamining) {
14638             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14639             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14640                      PieceToChar(selection), AAA + x, ONE + y);
14641             SendToICS(buf);
14642         } else {
14643             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14644                 int n;
14645                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14646                     n = PieceToNumber(selection - BlackPawn);
14647                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14648                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14649                     boards[0][BOARD_HEIGHT-1-n][1]++;
14650                 } else
14651                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14652                     n = PieceToNumber(selection);
14653                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14654                     boards[0][n][BOARD_WIDTH-1] = selection;
14655                     boards[0][n][BOARD_WIDTH-2]++;
14656                 }
14657             } else
14658             boards[0][y][x] = selection;
14659             DrawPosition(TRUE, boards[0]);
14660             ClearHighlights();
14661             fromX = fromY = -1;
14662         }
14663         break;
14664     }
14665 }
14666
14667
14668 void
14669 DropMenuEvent (ChessSquare selection, int x, int y)
14670 {
14671     ChessMove moveType;
14672
14673     switch (gameMode) {
14674       case IcsPlayingWhite:
14675       case MachinePlaysBlack:
14676         if (!WhiteOnMove(currentMove)) {
14677             DisplayMoveError(_("It is Black's turn"));
14678             return;
14679         }
14680         moveType = WhiteDrop;
14681         break;
14682       case IcsPlayingBlack:
14683       case MachinePlaysWhite:
14684         if (WhiteOnMove(currentMove)) {
14685             DisplayMoveError(_("It is White's turn"));
14686             return;
14687         }
14688         moveType = BlackDrop;
14689         break;
14690       case EditGame:
14691         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14692         break;
14693       default:
14694         return;
14695     }
14696
14697     if (moveType == BlackDrop && selection < BlackPawn) {
14698       selection = (ChessSquare) ((int) selection
14699                                  + (int) BlackPawn - (int) WhitePawn);
14700     }
14701     if (boards[currentMove][y][x] != EmptySquare) {
14702         DisplayMoveError(_("That square is occupied"));
14703         return;
14704     }
14705
14706     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14707 }
14708
14709 void
14710 AcceptEvent ()
14711 {
14712     /* Accept a pending offer of any kind from opponent */
14713
14714     if (appData.icsActive) {
14715         SendToICS(ics_prefix);
14716         SendToICS("accept\n");
14717     } else if (cmailMsgLoaded) {
14718         if (currentMove == cmailOldMove &&
14719             commentList[cmailOldMove] != NULL &&
14720             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14721                    "Black offers a draw" : "White offers a draw")) {
14722             TruncateGame();
14723             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14724             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14725         } else {
14726             DisplayError(_("There is no pending offer on this move"), 0);
14727             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14728         }
14729     } else {
14730         /* Not used for offers from chess program */
14731     }
14732 }
14733
14734 void
14735 DeclineEvent ()
14736 {
14737     /* Decline a pending offer of any kind from opponent */
14738
14739     if (appData.icsActive) {
14740         SendToICS(ics_prefix);
14741         SendToICS("decline\n");
14742     } else if (cmailMsgLoaded) {
14743         if (currentMove == cmailOldMove &&
14744             commentList[cmailOldMove] != NULL &&
14745             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14746                    "Black offers a draw" : "White offers a draw")) {
14747 #ifdef NOTDEF
14748             AppendComment(cmailOldMove, "Draw declined", TRUE);
14749             DisplayComment(cmailOldMove - 1, "Draw declined");
14750 #endif /*NOTDEF*/
14751         } else {
14752             DisplayError(_("There is no pending offer on this move"), 0);
14753         }
14754     } else {
14755         /* Not used for offers from chess program */
14756     }
14757 }
14758
14759 void
14760 RematchEvent ()
14761 {
14762     /* Issue ICS rematch command */
14763     if (appData.icsActive) {
14764         SendToICS(ics_prefix);
14765         SendToICS("rematch\n");
14766     }
14767 }
14768
14769 void
14770 CallFlagEvent ()
14771 {
14772     /* Call your opponent's flag (claim a win on time) */
14773     if (appData.icsActive) {
14774         SendToICS(ics_prefix);
14775         SendToICS("flag\n");
14776     } else {
14777         switch (gameMode) {
14778           default:
14779             return;
14780           case MachinePlaysWhite:
14781             if (whiteFlag) {
14782                 if (blackFlag)
14783                   GameEnds(GameIsDrawn, "Both players ran out of time",
14784                            GE_PLAYER);
14785                 else
14786                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14787             } else {
14788                 DisplayError(_("Your opponent is not out of time"), 0);
14789             }
14790             break;
14791           case MachinePlaysBlack:
14792             if (blackFlag) {
14793                 if (whiteFlag)
14794                   GameEnds(GameIsDrawn, "Both players ran out of time",
14795                            GE_PLAYER);
14796                 else
14797                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14798             } else {
14799                 DisplayError(_("Your opponent is not out of time"), 0);
14800             }
14801             break;
14802         }
14803     }
14804 }
14805
14806 void
14807 ClockClick (int which)
14808 {       // [HGM] code moved to back-end from winboard.c
14809         if(which) { // black clock
14810           if (gameMode == EditPosition || gameMode == IcsExamining) {
14811             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14812             SetBlackToPlayEvent();
14813           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14814           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14815           } else if (shiftKey) {
14816             AdjustClock(which, -1);
14817           } else if (gameMode == IcsPlayingWhite ||
14818                      gameMode == MachinePlaysBlack) {
14819             CallFlagEvent();
14820           }
14821         } else { // white clock
14822           if (gameMode == EditPosition || gameMode == IcsExamining) {
14823             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14824             SetWhiteToPlayEvent();
14825           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14826           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14827           } else if (shiftKey) {
14828             AdjustClock(which, -1);
14829           } else if (gameMode == IcsPlayingBlack ||
14830                    gameMode == MachinePlaysWhite) {
14831             CallFlagEvent();
14832           }
14833         }
14834 }
14835
14836 void
14837 DrawEvent ()
14838 {
14839     /* Offer draw or accept pending draw offer from opponent */
14840
14841     if (appData.icsActive) {
14842         /* Note: tournament rules require draw offers to be
14843            made after you make your move but before you punch
14844            your clock.  Currently ICS doesn't let you do that;
14845            instead, you immediately punch your clock after making
14846            a move, but you can offer a draw at any time. */
14847
14848         SendToICS(ics_prefix);
14849         SendToICS("draw\n");
14850         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14851     } else if (cmailMsgLoaded) {
14852         if (currentMove == cmailOldMove &&
14853             commentList[cmailOldMove] != NULL &&
14854             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14855                    "Black offers a draw" : "White offers a draw")) {
14856             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14857             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14858         } else if (currentMove == cmailOldMove + 1) {
14859             char *offer = WhiteOnMove(cmailOldMove) ?
14860               "White offers a draw" : "Black offers a draw";
14861             AppendComment(currentMove, offer, TRUE);
14862             DisplayComment(currentMove - 1, offer);
14863             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14864         } else {
14865             DisplayError(_("You must make your move before offering a draw"), 0);
14866             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14867         }
14868     } else if (first.offeredDraw) {
14869         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14870     } else {
14871         if (first.sendDrawOffers) {
14872             SendToProgram("draw\n", &first);
14873             userOfferedDraw = TRUE;
14874         }
14875     }
14876 }
14877
14878 void
14879 AdjournEvent ()
14880 {
14881     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14882
14883     if (appData.icsActive) {
14884         SendToICS(ics_prefix);
14885         SendToICS("adjourn\n");
14886     } else {
14887         /* Currently GNU Chess doesn't offer or accept Adjourns */
14888     }
14889 }
14890
14891
14892 void
14893 AbortEvent ()
14894 {
14895     /* Offer Abort or accept pending Abort offer from opponent */
14896
14897     if (appData.icsActive) {
14898         SendToICS(ics_prefix);
14899         SendToICS("abort\n");
14900     } else {
14901         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14902     }
14903 }
14904
14905 void
14906 ResignEvent ()
14907 {
14908     /* Resign.  You can do this even if it's not your turn. */
14909
14910     if (appData.icsActive) {
14911         SendToICS(ics_prefix);
14912         SendToICS("resign\n");
14913     } else {
14914         switch (gameMode) {
14915           case MachinePlaysWhite:
14916             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14917             break;
14918           case MachinePlaysBlack:
14919             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14920             break;
14921           case EditGame:
14922             if (cmailMsgLoaded) {
14923                 TruncateGame();
14924                 if (WhiteOnMove(cmailOldMove)) {
14925                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14926                 } else {
14927                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14928                 }
14929                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14930             }
14931             break;
14932           default:
14933             break;
14934         }
14935     }
14936 }
14937
14938
14939 void
14940 StopObservingEvent ()
14941 {
14942     /* Stop observing current games */
14943     SendToICS(ics_prefix);
14944     SendToICS("unobserve\n");
14945 }
14946
14947 void
14948 StopExaminingEvent ()
14949 {
14950     /* Stop observing current game */
14951     SendToICS(ics_prefix);
14952     SendToICS("unexamine\n");
14953 }
14954
14955 void
14956 ForwardInner (int target)
14957 {
14958     int limit; int oldSeekGraphUp = seekGraphUp;
14959
14960     if (appData.debugMode)
14961         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14962                 target, currentMove, forwardMostMove);
14963
14964     if (gameMode == EditPosition)
14965       return;
14966
14967     seekGraphUp = FALSE;
14968     MarkTargetSquares(1);
14969
14970     if (gameMode == PlayFromGameFile && !pausing)
14971       PauseEvent();
14972
14973     if (gameMode == IcsExamining && pausing)
14974       limit = pauseExamForwardMostMove;
14975     else
14976       limit = forwardMostMove;
14977
14978     if (target > limit) target = limit;
14979
14980     if (target > 0 && moveList[target - 1][0]) {
14981         int fromX, fromY, toX, toY;
14982         toX = moveList[target - 1][2] - AAA;
14983         toY = moveList[target - 1][3] - ONE;
14984         if (moveList[target - 1][1] == '@') {
14985             if (appData.highlightLastMove) {
14986                 SetHighlights(-1, -1, toX, toY);
14987             }
14988         } else {
14989             fromX = moveList[target - 1][0] - AAA;
14990             fromY = moveList[target - 1][1] - ONE;
14991             if (target == currentMove + 1) {
14992                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14993             }
14994             if (appData.highlightLastMove) {
14995                 SetHighlights(fromX, fromY, toX, toY);
14996             }
14997         }
14998     }
14999     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15000         gameMode == Training || gameMode == PlayFromGameFile ||
15001         gameMode == AnalyzeFile) {
15002         while (currentMove < target) {
15003             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15004             SendMoveToProgram(currentMove++, &first);
15005         }
15006     } else {
15007         currentMove = target;
15008     }
15009
15010     if (gameMode == EditGame || gameMode == EndOfGame) {
15011         whiteTimeRemaining = timeRemaining[0][currentMove];
15012         blackTimeRemaining = timeRemaining[1][currentMove];
15013     }
15014     DisplayBothClocks();
15015     DisplayMove(currentMove - 1);
15016     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15017     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15018     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15019         DisplayComment(currentMove - 1, commentList[currentMove]);
15020     }
15021     ClearMap(); // [HGM] exclude: invalidate map
15022 }
15023
15024
15025 void
15026 ForwardEvent ()
15027 {
15028     if (gameMode == IcsExamining && !pausing) {
15029         SendToICS(ics_prefix);
15030         SendToICS("forward\n");
15031     } else {
15032         ForwardInner(currentMove + 1);
15033     }
15034 }
15035
15036 void
15037 ToEndEvent ()
15038 {
15039     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15040         /* to optimze, we temporarily turn off analysis mode while we feed
15041          * the remaining moves to the engine. Otherwise we get analysis output
15042          * after each move.
15043          */
15044         if (first.analysisSupport) {
15045           SendToProgram("exit\nforce\n", &first);
15046           first.analyzing = FALSE;
15047         }
15048     }
15049
15050     if (gameMode == IcsExamining && !pausing) {
15051         SendToICS(ics_prefix);
15052         SendToICS("forward 999999\n");
15053     } else {
15054         ForwardInner(forwardMostMove);
15055     }
15056
15057     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15058         /* we have fed all the moves, so reactivate analysis mode */
15059         SendToProgram("analyze\n", &first);
15060         first.analyzing = TRUE;
15061         /*first.maybeThinking = TRUE;*/
15062         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15063     }
15064 }
15065
15066 void
15067 BackwardInner (int target)
15068 {
15069     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15070
15071     if (appData.debugMode)
15072         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15073                 target, currentMove, forwardMostMove);
15074
15075     if (gameMode == EditPosition) return;
15076     seekGraphUp = FALSE;
15077     MarkTargetSquares(1);
15078     if (currentMove <= backwardMostMove) {
15079         ClearHighlights();
15080         DrawPosition(full_redraw, boards[currentMove]);
15081         return;
15082     }
15083     if (gameMode == PlayFromGameFile && !pausing)
15084       PauseEvent();
15085
15086     if (moveList[target][0]) {
15087         int fromX, fromY, toX, toY;
15088         toX = moveList[target][2] - AAA;
15089         toY = moveList[target][3] - ONE;
15090         if (moveList[target][1] == '@') {
15091             if (appData.highlightLastMove) {
15092                 SetHighlights(-1, -1, toX, toY);
15093             }
15094         } else {
15095             fromX = moveList[target][0] - AAA;
15096             fromY = moveList[target][1] - ONE;
15097             if (target == currentMove - 1) {
15098                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15099             }
15100             if (appData.highlightLastMove) {
15101                 SetHighlights(fromX, fromY, toX, toY);
15102             }
15103         }
15104     }
15105     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15106         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15107         while (currentMove > target) {
15108             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15109                 // null move cannot be undone. Reload program with move history before it.
15110                 int i;
15111                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15112                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15113                 }
15114                 SendBoard(&first, i);
15115               if(second.analyzing) SendBoard(&second, i);
15116                 for(currentMove=i; currentMove<target; currentMove++) {
15117                     SendMoveToProgram(currentMove, &first);
15118                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15119                 }
15120                 break;
15121             }
15122             SendToBoth("undo\n");
15123             currentMove--;
15124         }
15125     } else {
15126         currentMove = target;
15127     }
15128
15129     if (gameMode == EditGame || gameMode == EndOfGame) {
15130         whiteTimeRemaining = timeRemaining[0][currentMove];
15131         blackTimeRemaining = timeRemaining[1][currentMove];
15132     }
15133     DisplayBothClocks();
15134     DisplayMove(currentMove - 1);
15135     DrawPosition(full_redraw, boards[currentMove]);
15136     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15137     // [HGM] PV info: routine tests if comment empty
15138     DisplayComment(currentMove - 1, commentList[currentMove]);
15139     ClearMap(); // [HGM] exclude: invalidate map
15140 }
15141
15142 void
15143 BackwardEvent ()
15144 {
15145     if (gameMode == IcsExamining && !pausing) {
15146         SendToICS(ics_prefix);
15147         SendToICS("backward\n");
15148     } else {
15149         BackwardInner(currentMove - 1);
15150     }
15151 }
15152
15153 void
15154 ToStartEvent ()
15155 {
15156     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15157         /* to optimize, we temporarily turn off analysis mode while we undo
15158          * all the moves. Otherwise we get analysis output after each undo.
15159          */
15160         if (first.analysisSupport) {
15161           SendToProgram("exit\nforce\n", &first);
15162           first.analyzing = FALSE;
15163         }
15164     }
15165
15166     if (gameMode == IcsExamining && !pausing) {
15167         SendToICS(ics_prefix);
15168         SendToICS("backward 999999\n");
15169     } else {
15170         BackwardInner(backwardMostMove);
15171     }
15172
15173     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15174         /* we have fed all the moves, so reactivate analysis mode */
15175         SendToProgram("analyze\n", &first);
15176         first.analyzing = TRUE;
15177         /*first.maybeThinking = TRUE;*/
15178         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15179     }
15180 }
15181
15182 void
15183 ToNrEvent (int to)
15184 {
15185   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15186   if (to >= forwardMostMove) to = forwardMostMove;
15187   if (to <= backwardMostMove) to = backwardMostMove;
15188   if (to < currentMove) {
15189     BackwardInner(to);
15190   } else {
15191     ForwardInner(to);
15192   }
15193 }
15194
15195 void
15196 RevertEvent (Boolean annotate)
15197 {
15198     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15199         return;
15200     }
15201     if (gameMode != IcsExamining) {
15202         DisplayError(_("You are not examining a game"), 0);
15203         return;
15204     }
15205     if (pausing) {
15206         DisplayError(_("You can't revert while pausing"), 0);
15207         return;
15208     }
15209     SendToICS(ics_prefix);
15210     SendToICS("revert\n");
15211 }
15212
15213 void
15214 RetractMoveEvent ()
15215 {
15216     switch (gameMode) {
15217       case MachinePlaysWhite:
15218       case MachinePlaysBlack:
15219         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15220             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15221             return;
15222         }
15223         if (forwardMostMove < 2) return;
15224         currentMove = forwardMostMove = forwardMostMove - 2;
15225         whiteTimeRemaining = timeRemaining[0][currentMove];
15226         blackTimeRemaining = timeRemaining[1][currentMove];
15227         DisplayBothClocks();
15228         DisplayMove(currentMove - 1);
15229         ClearHighlights();/*!! could figure this out*/
15230         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15231         SendToProgram("remove\n", &first);
15232         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15233         break;
15234
15235       case BeginningOfGame:
15236       default:
15237         break;
15238
15239       case IcsPlayingWhite:
15240       case IcsPlayingBlack:
15241         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15242             SendToICS(ics_prefix);
15243             SendToICS("takeback 2\n");
15244         } else {
15245             SendToICS(ics_prefix);
15246             SendToICS("takeback 1\n");
15247         }
15248         break;
15249     }
15250 }
15251
15252 void
15253 MoveNowEvent ()
15254 {
15255     ChessProgramState *cps;
15256
15257     switch (gameMode) {
15258       case MachinePlaysWhite:
15259         if (!WhiteOnMove(forwardMostMove)) {
15260             DisplayError(_("It is your turn"), 0);
15261             return;
15262         }
15263         cps = &first;
15264         break;
15265       case MachinePlaysBlack:
15266         if (WhiteOnMove(forwardMostMove)) {
15267             DisplayError(_("It is your turn"), 0);
15268             return;
15269         }
15270         cps = &first;
15271         break;
15272       case TwoMachinesPlay:
15273         if (WhiteOnMove(forwardMostMove) ==
15274             (first.twoMachinesColor[0] == 'w')) {
15275             cps = &first;
15276         } else {
15277             cps = &second;
15278         }
15279         break;
15280       case BeginningOfGame:
15281       default:
15282         return;
15283     }
15284     SendToProgram("?\n", cps);
15285 }
15286
15287 void
15288 TruncateGameEvent ()
15289 {
15290     EditGameEvent();
15291     if (gameMode != EditGame) return;
15292     TruncateGame();
15293 }
15294
15295 void
15296 TruncateGame ()
15297 {
15298     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15299     if (forwardMostMove > currentMove) {
15300         if (gameInfo.resultDetails != NULL) {
15301             free(gameInfo.resultDetails);
15302             gameInfo.resultDetails = NULL;
15303             gameInfo.result = GameUnfinished;
15304         }
15305         forwardMostMove = currentMove;
15306         HistorySet(parseList, backwardMostMove, forwardMostMove,
15307                    currentMove-1);
15308     }
15309 }
15310
15311 void
15312 HintEvent ()
15313 {
15314     if (appData.noChessProgram) return;
15315     switch (gameMode) {
15316       case MachinePlaysWhite:
15317         if (WhiteOnMove(forwardMostMove)) {
15318             DisplayError(_("Wait until your turn"), 0);
15319             return;
15320         }
15321         break;
15322       case BeginningOfGame:
15323       case MachinePlaysBlack:
15324         if (!WhiteOnMove(forwardMostMove)) {
15325             DisplayError(_("Wait until your turn"), 0);
15326             return;
15327         }
15328         break;
15329       default:
15330         DisplayError(_("No hint available"), 0);
15331         return;
15332     }
15333     SendToProgram("hint\n", &first);
15334     hintRequested = TRUE;
15335 }
15336
15337 void
15338 CreateBookEvent ()
15339 {
15340     ListGame * lg = (ListGame *) gameList.head;
15341     FILE *f, *g;
15342     int nItem;
15343     static int secondTime = FALSE;
15344
15345     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15346         DisplayError(_("Game list not loaded or empty"), 0);
15347         return;
15348     }
15349
15350     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15351         fclose(g);
15352         secondTime++;
15353         DisplayNote(_("Book file exists! Try again for overwrite."));
15354         return;
15355     }
15356
15357     creatingBook = TRUE;
15358     secondTime = FALSE;
15359
15360     /* Get list size */
15361     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15362         LoadGame(f, nItem, "", TRUE);
15363         AddGameToBook(TRUE);
15364         lg = (ListGame *) lg->node.succ;
15365     }
15366
15367     creatingBook = FALSE;
15368     FlushBook();
15369 }
15370
15371 void
15372 BookEvent ()
15373 {
15374     if (appData.noChessProgram) return;
15375     switch (gameMode) {
15376       case MachinePlaysWhite:
15377         if (WhiteOnMove(forwardMostMove)) {
15378             DisplayError(_("Wait until your turn"), 0);
15379             return;
15380         }
15381         break;
15382       case BeginningOfGame:
15383       case MachinePlaysBlack:
15384         if (!WhiteOnMove(forwardMostMove)) {
15385             DisplayError(_("Wait until your turn"), 0);
15386             return;
15387         }
15388         break;
15389       case EditPosition:
15390         EditPositionDone(TRUE);
15391         break;
15392       case TwoMachinesPlay:
15393         return;
15394       default:
15395         break;
15396     }
15397     SendToProgram("bk\n", &first);
15398     bookOutput[0] = NULLCHAR;
15399     bookRequested = TRUE;
15400 }
15401
15402 void
15403 AboutGameEvent ()
15404 {
15405     char *tags = PGNTags(&gameInfo);
15406     TagsPopUp(tags, CmailMsg());
15407     free(tags);
15408 }
15409
15410 /* end button procedures */
15411
15412 void
15413 PrintPosition (FILE *fp, int move)
15414 {
15415     int i, j;
15416
15417     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15418         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15419             char c = PieceToChar(boards[move][i][j]);
15420             fputc(c == 'x' ? '.' : c, fp);
15421             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15422         }
15423     }
15424     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15425       fprintf(fp, "white to play\n");
15426     else
15427       fprintf(fp, "black to play\n");
15428 }
15429
15430 void
15431 PrintOpponents (FILE *fp)
15432 {
15433     if (gameInfo.white != NULL) {
15434         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15435     } else {
15436         fprintf(fp, "\n");
15437     }
15438 }
15439
15440 /* Find last component of program's own name, using some heuristics */
15441 void
15442 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15443 {
15444     char *p, *q, c;
15445     int local = (strcmp(host, "localhost") == 0);
15446     while (!local && (p = strchr(prog, ';')) != NULL) {
15447         p++;
15448         while (*p == ' ') p++;
15449         prog = p;
15450     }
15451     if (*prog == '"' || *prog == '\'') {
15452         q = strchr(prog + 1, *prog);
15453     } else {
15454         q = strchr(prog, ' ');
15455     }
15456     if (q == NULL) q = prog + strlen(prog);
15457     p = q;
15458     while (p >= prog && *p != '/' && *p != '\\') p--;
15459     p++;
15460     if(p == prog && *p == '"') p++;
15461     c = *q; *q = 0;
15462     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15463     memcpy(buf, p, q - p);
15464     buf[q - p] = NULLCHAR;
15465     if (!local) {
15466         strcat(buf, "@");
15467         strcat(buf, host);
15468     }
15469 }
15470
15471 char *
15472 TimeControlTagValue ()
15473 {
15474     char buf[MSG_SIZ];
15475     if (!appData.clockMode) {
15476       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15477     } else if (movesPerSession > 0) {
15478       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15479     } else if (timeIncrement == 0) {
15480       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15481     } else {
15482       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15483     }
15484     return StrSave(buf);
15485 }
15486
15487 void
15488 SetGameInfo ()
15489 {
15490     /* This routine is used only for certain modes */
15491     VariantClass v = gameInfo.variant;
15492     ChessMove r = GameUnfinished;
15493     char *p = NULL;
15494
15495     if(keepInfo) return;
15496
15497     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15498         r = gameInfo.result;
15499         p = gameInfo.resultDetails;
15500         gameInfo.resultDetails = NULL;
15501     }
15502     ClearGameInfo(&gameInfo);
15503     gameInfo.variant = v;
15504
15505     switch (gameMode) {
15506       case MachinePlaysWhite:
15507         gameInfo.event = StrSave( appData.pgnEventHeader );
15508         gameInfo.site = StrSave(HostName());
15509         gameInfo.date = PGNDate();
15510         gameInfo.round = StrSave("-");
15511         gameInfo.white = StrSave(first.tidy);
15512         gameInfo.black = StrSave(UserName());
15513         gameInfo.timeControl = TimeControlTagValue();
15514         break;
15515
15516       case MachinePlaysBlack:
15517         gameInfo.event = StrSave( appData.pgnEventHeader );
15518         gameInfo.site = StrSave(HostName());
15519         gameInfo.date = PGNDate();
15520         gameInfo.round = StrSave("-");
15521         gameInfo.white = StrSave(UserName());
15522         gameInfo.black = StrSave(first.tidy);
15523         gameInfo.timeControl = TimeControlTagValue();
15524         break;
15525
15526       case TwoMachinesPlay:
15527         gameInfo.event = StrSave( appData.pgnEventHeader );
15528         gameInfo.site = StrSave(HostName());
15529         gameInfo.date = PGNDate();
15530         if (roundNr > 0) {
15531             char buf[MSG_SIZ];
15532             snprintf(buf, MSG_SIZ, "%d", roundNr);
15533             gameInfo.round = StrSave(buf);
15534         } else {
15535             gameInfo.round = StrSave("-");
15536         }
15537         if (first.twoMachinesColor[0] == 'w') {
15538             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15539             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15540         } else {
15541             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15542             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15543         }
15544         gameInfo.timeControl = TimeControlTagValue();
15545         break;
15546
15547       case EditGame:
15548         gameInfo.event = StrSave("Edited game");
15549         gameInfo.site = StrSave(HostName());
15550         gameInfo.date = PGNDate();
15551         gameInfo.round = StrSave("-");
15552         gameInfo.white = StrSave("-");
15553         gameInfo.black = StrSave("-");
15554         gameInfo.result = r;
15555         gameInfo.resultDetails = p;
15556         break;
15557
15558       case EditPosition:
15559         gameInfo.event = StrSave("Edited position");
15560         gameInfo.site = StrSave(HostName());
15561         gameInfo.date = PGNDate();
15562         gameInfo.round = StrSave("-");
15563         gameInfo.white = StrSave("-");
15564         gameInfo.black = StrSave("-");
15565         break;
15566
15567       case IcsPlayingWhite:
15568       case IcsPlayingBlack:
15569       case IcsObserving:
15570       case IcsExamining:
15571         break;
15572
15573       case PlayFromGameFile:
15574         gameInfo.event = StrSave("Game from non-PGN file");
15575         gameInfo.site = StrSave(HostName());
15576         gameInfo.date = PGNDate();
15577         gameInfo.round = StrSave("-");
15578         gameInfo.white = StrSave("?");
15579         gameInfo.black = StrSave("?");
15580         break;
15581
15582       default:
15583         break;
15584     }
15585 }
15586
15587 void
15588 ReplaceComment (int index, char *text)
15589 {
15590     int len;
15591     char *p;
15592     float score;
15593
15594     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15595        pvInfoList[index-1].depth == len &&
15596        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15597        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15598     while (*text == '\n') text++;
15599     len = strlen(text);
15600     while (len > 0 && text[len - 1] == '\n') len--;
15601
15602     if (commentList[index] != NULL)
15603       free(commentList[index]);
15604
15605     if (len == 0) {
15606         commentList[index] = NULL;
15607         return;
15608     }
15609   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15610       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15611       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15612     commentList[index] = (char *) malloc(len + 2);
15613     strncpy(commentList[index], text, len);
15614     commentList[index][len] = '\n';
15615     commentList[index][len + 1] = NULLCHAR;
15616   } else {
15617     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15618     char *p;
15619     commentList[index] = (char *) malloc(len + 7);
15620     safeStrCpy(commentList[index], "{\n", 3);
15621     safeStrCpy(commentList[index]+2, text, len+1);
15622     commentList[index][len+2] = NULLCHAR;
15623     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15624     strcat(commentList[index], "\n}\n");
15625   }
15626 }
15627
15628 void
15629 CrushCRs (char *text)
15630 {
15631   char *p = text;
15632   char *q = text;
15633   char ch;
15634
15635   do {
15636     ch = *p++;
15637     if (ch == '\r') continue;
15638     *q++ = ch;
15639   } while (ch != '\0');
15640 }
15641
15642 void
15643 AppendComment (int index, char *text, Boolean addBraces)
15644 /* addBraces  tells if we should add {} */
15645 {
15646     int oldlen, len;
15647     char *old;
15648
15649 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15650     if(addBraces == 3) addBraces = 0; else // force appending literally
15651     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15652
15653     CrushCRs(text);
15654     while (*text == '\n') text++;
15655     len = strlen(text);
15656     while (len > 0 && text[len - 1] == '\n') len--;
15657     text[len] = NULLCHAR;
15658
15659     if (len == 0) return;
15660
15661     if (commentList[index] != NULL) {
15662       Boolean addClosingBrace = addBraces;
15663         old = commentList[index];
15664         oldlen = strlen(old);
15665         while(commentList[index][oldlen-1] ==  '\n')
15666           commentList[index][--oldlen] = NULLCHAR;
15667         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15668         safeStrCpy(commentList[index], old, oldlen + len + 6);
15669         free(old);
15670         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15671         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15672           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15673           while (*text == '\n') { text++; len--; }
15674           commentList[index][--oldlen] = NULLCHAR;
15675       }
15676         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15677         else          strcat(commentList[index], "\n");
15678         strcat(commentList[index], text);
15679         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15680         else          strcat(commentList[index], "\n");
15681     } else {
15682         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15683         if(addBraces)
15684           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15685         else commentList[index][0] = NULLCHAR;
15686         strcat(commentList[index], text);
15687         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15688         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15689     }
15690 }
15691
15692 static char *
15693 FindStr (char * text, char * sub_text)
15694 {
15695     char * result = strstr( text, sub_text );
15696
15697     if( result != NULL ) {
15698         result += strlen( sub_text );
15699     }
15700
15701     return result;
15702 }
15703
15704 /* [AS] Try to extract PV info from PGN comment */
15705 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15706 char *
15707 GetInfoFromComment (int index, char * text)
15708 {
15709     char * sep = text, *p;
15710
15711     if( text != NULL && index > 0 ) {
15712         int score = 0;
15713         int depth = 0;
15714         int time = -1, sec = 0, deci;
15715         char * s_eval = FindStr( text, "[%eval " );
15716         char * s_emt = FindStr( text, "[%emt " );
15717 #if 0
15718         if( s_eval != NULL || s_emt != NULL ) {
15719 #else
15720         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15721 #endif
15722             /* New style */
15723             char delim;
15724
15725             if( s_eval != NULL ) {
15726                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15727                     return text;
15728                 }
15729
15730                 if( delim != ']' ) {
15731                     return text;
15732                 }
15733             }
15734
15735             if( s_emt != NULL ) {
15736             }
15737                 return text;
15738         }
15739         else {
15740             /* We expect something like: [+|-]nnn.nn/dd */
15741             int score_lo = 0;
15742
15743             if(*text != '{') return text; // [HGM] braces: must be normal comment
15744
15745             sep = strchr( text, '/' );
15746             if( sep == NULL || sep < (text+4) ) {
15747                 return text;
15748             }
15749
15750             p = text;
15751             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15752             if(p[1] == '(') { // comment starts with PV
15753                p = strchr(p, ')'); // locate end of PV
15754                if(p == NULL || sep < p+5) return text;
15755                // at this point we have something like "{(.*) +0.23/6 ..."
15756                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15757                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15758                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15759             }
15760             time = -1; sec = -1; deci = -1;
15761             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15762                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15763                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15764                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15765                 return text;
15766             }
15767
15768             if( score_lo < 0 || score_lo >= 100 ) {
15769                 return text;
15770             }
15771
15772             if(sec >= 0) time = 600*time + 10*sec; else
15773             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15774
15775             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15776
15777             /* [HGM] PV time: now locate end of PV info */
15778             while( *++sep >= '0' && *sep <= '9'); // strip depth
15779             if(time >= 0)
15780             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15781             if(sec >= 0)
15782             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15783             if(deci >= 0)
15784             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15785             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15786         }
15787
15788         if( depth <= 0 ) {
15789             return text;
15790         }
15791
15792         if( time < 0 ) {
15793             time = -1;
15794         }
15795
15796         pvInfoList[index-1].depth = depth;
15797         pvInfoList[index-1].score = score;
15798         pvInfoList[index-1].time  = 10*time; // centi-sec
15799         if(*sep == '}') *sep = 0; else *--sep = '{';
15800         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15801     }
15802     return sep;
15803 }
15804
15805 void
15806 SendToProgram (char *message, ChessProgramState *cps)
15807 {
15808     int count, outCount, error;
15809     char buf[MSG_SIZ];
15810
15811     if (cps->pr == NoProc) return;
15812     Attention(cps);
15813
15814     if (appData.debugMode) {
15815         TimeMark now;
15816         GetTimeMark(&now);
15817         fprintf(debugFP, "%ld >%-6s: %s",
15818                 SubtractTimeMarks(&now, &programStartTime),
15819                 cps->which, message);
15820         if(serverFP)
15821             fprintf(serverFP, "%ld >%-6s: %s",
15822                 SubtractTimeMarks(&now, &programStartTime),
15823                 cps->which, message), fflush(serverFP);
15824     }
15825
15826     count = strlen(message);
15827     outCount = OutputToProcess(cps->pr, message, count, &error);
15828     if (outCount < count && !exiting
15829                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15830       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15831       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15832         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15833             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15834                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15835                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15836                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15837             } else {
15838                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15839                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15840                 gameInfo.result = res;
15841             }
15842             gameInfo.resultDetails = StrSave(buf);
15843         }
15844         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15845         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15846     }
15847 }
15848
15849 void
15850 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15851 {
15852     char *end_str;
15853     char buf[MSG_SIZ];
15854     ChessProgramState *cps = (ChessProgramState *)closure;
15855
15856     if (isr != cps->isr) return; /* Killed intentionally */
15857     if (count <= 0) {
15858         if (count == 0) {
15859             RemoveInputSource(cps->isr);
15860             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15861                     _(cps->which), cps->program);
15862             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15863             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15864                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15865                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15866                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15867                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15868                 } else {
15869                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15870                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15871                     gameInfo.result = res;
15872                 }
15873                 gameInfo.resultDetails = StrSave(buf);
15874             }
15875             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15876             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15877         } else {
15878             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15879                     _(cps->which), cps->program);
15880             RemoveInputSource(cps->isr);
15881
15882             /* [AS] Program is misbehaving badly... kill it */
15883             if( count == -2 ) {
15884                 DestroyChildProcess( cps->pr, 9 );
15885                 cps->pr = NoProc;
15886             }
15887
15888             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15889         }
15890         return;
15891     }
15892
15893     if ((end_str = strchr(message, '\r')) != NULL)
15894       *end_str = NULLCHAR;
15895     if ((end_str = strchr(message, '\n')) != NULL)
15896       *end_str = NULLCHAR;
15897
15898     if (appData.debugMode) {
15899         TimeMark now; int print = 1;
15900         char *quote = ""; char c; int i;
15901
15902         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15903                 char start = message[0];
15904                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15905                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15906                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15907                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15908                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15909                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15910                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15911                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15912                    sscanf(message, "hint: %c", &c)!=1 &&
15913                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15914                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15915                     print = (appData.engineComments >= 2);
15916                 }
15917                 message[0] = start; // restore original message
15918         }
15919         if(print) {
15920                 GetTimeMark(&now);
15921                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15922                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15923                         quote,
15924                         message);
15925                 if(serverFP)
15926                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15927                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15928                         quote,
15929                         message), fflush(serverFP);
15930         }
15931     }
15932
15933     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15934     if (appData.icsEngineAnalyze) {
15935         if (strstr(message, "whisper") != NULL ||
15936              strstr(message, "kibitz") != NULL ||
15937             strstr(message, "tellics") != NULL) return;
15938     }
15939
15940     HandleMachineMove(message, cps);
15941 }
15942
15943
15944 void
15945 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15946 {
15947     char buf[MSG_SIZ];
15948     int seconds;
15949
15950     if( timeControl_2 > 0 ) {
15951         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15952             tc = timeControl_2;
15953         }
15954     }
15955     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15956     inc /= cps->timeOdds;
15957     st  /= cps->timeOdds;
15958
15959     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15960
15961     if (st > 0) {
15962       /* Set exact time per move, normally using st command */
15963       if (cps->stKludge) {
15964         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15965         seconds = st % 60;
15966         if (seconds == 0) {
15967           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15968         } else {
15969           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15970         }
15971       } else {
15972         snprintf(buf, MSG_SIZ, "st %d\n", st);
15973       }
15974     } else {
15975       /* Set conventional or incremental time control, using level command */
15976       if (seconds == 0) {
15977         /* Note old gnuchess bug -- minutes:seconds used to not work.
15978            Fixed in later versions, but still avoid :seconds
15979            when seconds is 0. */
15980         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15981       } else {
15982         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15983                  seconds, inc/1000.);
15984       }
15985     }
15986     SendToProgram(buf, cps);
15987
15988     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15989     /* Orthogonally, limit search to given depth */
15990     if (sd > 0) {
15991       if (cps->sdKludge) {
15992         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15993       } else {
15994         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15995       }
15996       SendToProgram(buf, cps);
15997     }
15998
15999     if(cps->nps >= 0) { /* [HGM] nps */
16000         if(cps->supportsNPS == FALSE)
16001           cps->nps = -1; // don't use if engine explicitly says not supported!
16002         else {
16003           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16004           SendToProgram(buf, cps);
16005         }
16006     }
16007 }
16008
16009 ChessProgramState *
16010 WhitePlayer ()
16011 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16012 {
16013     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16014        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16015         return &second;
16016     return &first;
16017 }
16018
16019 void
16020 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16021 {
16022     char message[MSG_SIZ];
16023     long time, otime;
16024
16025     /* Note: this routine must be called when the clocks are stopped
16026        or when they have *just* been set or switched; otherwise
16027        it will be off by the time since the current tick started.
16028     */
16029     if (machineWhite) {
16030         time = whiteTimeRemaining / 10;
16031         otime = blackTimeRemaining / 10;
16032     } else {
16033         time = blackTimeRemaining / 10;
16034         otime = whiteTimeRemaining / 10;
16035     }
16036     /* [HGM] translate opponent's time by time-odds factor */
16037     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16038
16039     if (time <= 0) time = 1;
16040     if (otime <= 0) otime = 1;
16041
16042     snprintf(message, MSG_SIZ, "time %ld\n", time);
16043     SendToProgram(message, cps);
16044
16045     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16046     SendToProgram(message, cps);
16047 }
16048
16049 int
16050 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16051 {
16052   char buf[MSG_SIZ];
16053   int len = strlen(name);
16054   int val;
16055
16056   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16057     (*p) += len + 1;
16058     sscanf(*p, "%d", &val);
16059     *loc = (val != 0);
16060     while (**p && **p != ' ')
16061       (*p)++;
16062     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16063     SendToProgram(buf, cps);
16064     return TRUE;
16065   }
16066   return FALSE;
16067 }
16068
16069 int
16070 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16071 {
16072   char buf[MSG_SIZ];
16073   int len = strlen(name);
16074   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16075     (*p) += len + 1;
16076     sscanf(*p, "%d", loc);
16077     while (**p && **p != ' ') (*p)++;
16078     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16079     SendToProgram(buf, cps);
16080     return TRUE;
16081   }
16082   return FALSE;
16083 }
16084
16085 int
16086 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16087 {
16088   char buf[MSG_SIZ];
16089   int len = strlen(name);
16090   if (strncmp((*p), name, len) == 0
16091       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16092     (*p) += len + 2;
16093     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16094     sscanf(*p, "%[^\"]", *loc);
16095     while (**p && **p != '\"') (*p)++;
16096     if (**p == '\"') (*p)++;
16097     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16098     SendToProgram(buf, cps);
16099     return TRUE;
16100   }
16101   return FALSE;
16102 }
16103
16104 int
16105 ParseOption (Option *opt, ChessProgramState *cps)
16106 // [HGM] options: process the string that defines an engine option, and determine
16107 // name, type, default value, and allowed value range
16108 {
16109         char *p, *q, buf[MSG_SIZ];
16110         int n, min = (-1)<<31, max = 1<<31, def;
16111
16112         if(p = strstr(opt->name, " -spin ")) {
16113             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16114             if(max < min) max = min; // enforce consistency
16115             if(def < min) def = min;
16116             if(def > max) def = max;
16117             opt->value = def;
16118             opt->min = min;
16119             opt->max = max;
16120             opt->type = Spin;
16121         } else if((p = strstr(opt->name, " -slider "))) {
16122             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16123             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16124             if(max < min) max = min; // enforce consistency
16125             if(def < min) def = min;
16126             if(def > max) def = max;
16127             opt->value = def;
16128             opt->min = min;
16129             opt->max = max;
16130             opt->type = Spin; // Slider;
16131         } else if((p = strstr(opt->name, " -string "))) {
16132             opt->textValue = p+9;
16133             opt->type = TextBox;
16134         } else if((p = strstr(opt->name, " -file "))) {
16135             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16136             opt->textValue = p+7;
16137             opt->type = FileName; // FileName;
16138         } else if((p = strstr(opt->name, " -path "))) {
16139             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16140             opt->textValue = p+7;
16141             opt->type = PathName; // PathName;
16142         } else if(p = strstr(opt->name, " -check ")) {
16143             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16144             opt->value = (def != 0);
16145             opt->type = CheckBox;
16146         } else if(p = strstr(opt->name, " -combo ")) {
16147             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16148             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16149             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16150             opt->value = n = 0;
16151             while(q = StrStr(q, " /// ")) {
16152                 n++; *q = 0;    // count choices, and null-terminate each of them
16153                 q += 5;
16154                 if(*q == '*') { // remember default, which is marked with * prefix
16155                     q++;
16156                     opt->value = n;
16157                 }
16158                 cps->comboList[cps->comboCnt++] = q;
16159             }
16160             cps->comboList[cps->comboCnt++] = NULL;
16161             opt->max = n + 1;
16162             opt->type = ComboBox;
16163         } else if(p = strstr(opt->name, " -button")) {
16164             opt->type = Button;
16165         } else if(p = strstr(opt->name, " -save")) {
16166             opt->type = SaveButton;
16167         } else return FALSE;
16168         *p = 0; // terminate option name
16169         // now look if the command-line options define a setting for this engine option.
16170         if(cps->optionSettings && cps->optionSettings[0])
16171             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16172         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16173           snprintf(buf, MSG_SIZ, "option %s", p);
16174                 if(p = strstr(buf, ",")) *p = 0;
16175                 if(q = strchr(buf, '=')) switch(opt->type) {
16176                     case ComboBox:
16177                         for(n=0; n<opt->max; n++)
16178                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16179                         break;
16180                     case TextBox:
16181                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16182                         break;
16183                     case Spin:
16184                     case CheckBox:
16185                         opt->value = atoi(q+1);
16186                     default:
16187                         break;
16188                 }
16189                 strcat(buf, "\n");
16190                 SendToProgram(buf, cps);
16191         }
16192         return TRUE;
16193 }
16194
16195 void
16196 FeatureDone (ChessProgramState *cps, int val)
16197 {
16198   DelayedEventCallback cb = GetDelayedEvent();
16199   if ((cb == InitBackEnd3 && cps == &first) ||
16200       (cb == SettingsMenuIfReady && cps == &second) ||
16201       (cb == LoadEngine) ||
16202       (cb == TwoMachinesEventIfReady)) {
16203     CancelDelayedEvent();
16204     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16205   }
16206   cps->initDone = val;
16207   if(val) cps->reload = FALSE;
16208 }
16209
16210 /* Parse feature command from engine */
16211 void
16212 ParseFeatures (char *args, ChessProgramState *cps)
16213 {
16214   char *p = args;
16215   char *q = NULL;
16216   int val;
16217   char buf[MSG_SIZ];
16218
16219   for (;;) {
16220     while (*p == ' ') p++;
16221     if (*p == NULLCHAR) return;
16222
16223     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16224     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16225     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16226     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16227     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16228     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16229     if (BoolFeature(&p, "reuse", &val, cps)) {
16230       /* Engine can disable reuse, but can't enable it if user said no */
16231       if (!val) cps->reuse = FALSE;
16232       continue;
16233     }
16234     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16235     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16236       if (gameMode == TwoMachinesPlay) {
16237         DisplayTwoMachinesTitle();
16238       } else {
16239         DisplayTitle("");
16240       }
16241       continue;
16242     }
16243     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16244     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16245     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16246     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16247     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16248     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16249     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16250     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16251     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16252     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16253     if (IntFeature(&p, "done", &val, cps)) {
16254       FeatureDone(cps, val);
16255       continue;
16256     }
16257     /* Added by Tord: */
16258     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16259     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16260     /* End of additions by Tord */
16261
16262     /* [HGM] added features: */
16263     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16264     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16265     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16266     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16267     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16268     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16269     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16270     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16271         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16272         FREE(cps->option[cps->nrOptions].name);
16273         cps->option[cps->nrOptions].name = q; q = NULL;
16274         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16275           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16276             SendToProgram(buf, cps);
16277             continue;
16278         }
16279         if(cps->nrOptions >= MAX_OPTIONS) {
16280             cps->nrOptions--;
16281             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16282             DisplayError(buf, 0);
16283         }
16284         continue;
16285     }
16286     /* End of additions by HGM */
16287
16288     /* unknown feature: complain and skip */
16289     q = p;
16290     while (*q && *q != '=') q++;
16291     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16292     SendToProgram(buf, cps);
16293     p = q;
16294     if (*p == '=') {
16295       p++;
16296       if (*p == '\"') {
16297         p++;
16298         while (*p && *p != '\"') p++;
16299         if (*p == '\"') p++;
16300       } else {
16301         while (*p && *p != ' ') p++;
16302       }
16303     }
16304   }
16305
16306 }
16307
16308 void
16309 PeriodicUpdatesEvent (int newState)
16310 {
16311     if (newState == appData.periodicUpdates)
16312       return;
16313
16314     appData.periodicUpdates=newState;
16315
16316     /* Display type changes, so update it now */
16317 //    DisplayAnalysis();
16318
16319     /* Get the ball rolling again... */
16320     if (newState) {
16321         AnalysisPeriodicEvent(1);
16322         StartAnalysisClock();
16323     }
16324 }
16325
16326 void
16327 PonderNextMoveEvent (int newState)
16328 {
16329     if (newState == appData.ponderNextMove) return;
16330     if (gameMode == EditPosition) EditPositionDone(TRUE);
16331     if (newState) {
16332         SendToProgram("hard\n", &first);
16333         if (gameMode == TwoMachinesPlay) {
16334             SendToProgram("hard\n", &second);
16335         }
16336     } else {
16337         SendToProgram("easy\n", &first);
16338         thinkOutput[0] = NULLCHAR;
16339         if (gameMode == TwoMachinesPlay) {
16340             SendToProgram("easy\n", &second);
16341         }
16342     }
16343     appData.ponderNextMove = newState;
16344 }
16345
16346 void
16347 NewSettingEvent (int option, int *feature, char *command, int value)
16348 {
16349     char buf[MSG_SIZ];
16350
16351     if (gameMode == EditPosition) EditPositionDone(TRUE);
16352     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16353     if(feature == NULL || *feature) SendToProgram(buf, &first);
16354     if (gameMode == TwoMachinesPlay) {
16355         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16356     }
16357 }
16358
16359 void
16360 ShowThinkingEvent ()
16361 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16362 {
16363     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16364     int newState = appData.showThinking
16365         // [HGM] thinking: other features now need thinking output as well
16366         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16367
16368     if (oldState == newState) return;
16369     oldState = newState;
16370     if (gameMode == EditPosition) EditPositionDone(TRUE);
16371     if (oldState) {
16372         SendToProgram("post\n", &first);
16373         if (gameMode == TwoMachinesPlay) {
16374             SendToProgram("post\n", &second);
16375         }
16376     } else {
16377         SendToProgram("nopost\n", &first);
16378         thinkOutput[0] = NULLCHAR;
16379         if (gameMode == TwoMachinesPlay) {
16380             SendToProgram("nopost\n", &second);
16381         }
16382     }
16383 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16384 }
16385
16386 void
16387 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16388 {
16389   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16390   if (pr == NoProc) return;
16391   AskQuestion(title, question, replyPrefix, pr);
16392 }
16393
16394 void
16395 TypeInEvent (char firstChar)
16396 {
16397     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16398         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16399         gameMode == AnalyzeMode || gameMode == EditGame ||
16400         gameMode == EditPosition || gameMode == IcsExamining ||
16401         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16402         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16403                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16404                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16405         gameMode == Training) PopUpMoveDialog(firstChar);
16406 }
16407
16408 void
16409 TypeInDoneEvent (char *move)
16410 {
16411         Board board;
16412         int n, fromX, fromY, toX, toY;
16413         char promoChar;
16414         ChessMove moveType;
16415
16416         // [HGM] FENedit
16417         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16418                 EditPositionPasteFEN(move);
16419                 return;
16420         }
16421         // [HGM] movenum: allow move number to be typed in any mode
16422         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16423           ToNrEvent(2*n-1);
16424           return;
16425         }
16426         // undocumented kludge: allow command-line option to be typed in!
16427         // (potentially fatal, and does not implement the effect of the option.)
16428         // should only be used for options that are values on which future decisions will be made,
16429         // and definitely not on options that would be used during initialization.
16430         if(strstr(move, "!!! -") == move) {
16431             ParseArgsFromString(move+4);
16432             return;
16433         }
16434
16435       if (gameMode != EditGame && currentMove != forwardMostMove &&
16436         gameMode != Training) {
16437         DisplayMoveError(_("Displayed move is not current"));
16438       } else {
16439         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16440           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16441         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16442         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16443           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16444           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16445         } else {
16446           DisplayMoveError(_("Could not parse move"));
16447         }
16448       }
16449 }
16450
16451 void
16452 DisplayMove (int moveNumber)
16453 {
16454     char message[MSG_SIZ];
16455     char res[MSG_SIZ];
16456     char cpThinkOutput[MSG_SIZ];
16457
16458     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16459
16460     if (moveNumber == forwardMostMove - 1 ||
16461         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16462
16463         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16464
16465         if (strchr(cpThinkOutput, '\n')) {
16466             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16467         }
16468     } else {
16469         *cpThinkOutput = NULLCHAR;
16470     }
16471
16472     /* [AS] Hide thinking from human user */
16473     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16474         *cpThinkOutput = NULLCHAR;
16475         if( thinkOutput[0] != NULLCHAR ) {
16476             int i;
16477
16478             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16479                 cpThinkOutput[i] = '.';
16480             }
16481             cpThinkOutput[i] = NULLCHAR;
16482             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16483         }
16484     }
16485
16486     if (moveNumber == forwardMostMove - 1 &&
16487         gameInfo.resultDetails != NULL) {
16488         if (gameInfo.resultDetails[0] == NULLCHAR) {
16489           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16490         } else {
16491           snprintf(res, MSG_SIZ, " {%s} %s",
16492                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16493         }
16494     } else {
16495         res[0] = NULLCHAR;
16496     }
16497
16498     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16499         DisplayMessage(res, cpThinkOutput);
16500     } else {
16501       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16502                 WhiteOnMove(moveNumber) ? " " : ".. ",
16503                 parseList[moveNumber], res);
16504         DisplayMessage(message, cpThinkOutput);
16505     }
16506 }
16507
16508 void
16509 DisplayComment (int moveNumber, char *text)
16510 {
16511     char title[MSG_SIZ];
16512
16513     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16514       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16515     } else {
16516       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16517               WhiteOnMove(moveNumber) ? " " : ".. ",
16518               parseList[moveNumber]);
16519     }
16520     if (text != NULL && (appData.autoDisplayComment || commentUp))
16521         CommentPopUp(title, text);
16522 }
16523
16524 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16525  * might be busy thinking or pondering.  It can be omitted if your
16526  * gnuchess is configured to stop thinking immediately on any user
16527  * input.  However, that gnuchess feature depends on the FIONREAD
16528  * ioctl, which does not work properly on some flavors of Unix.
16529  */
16530 void
16531 Attention (ChessProgramState *cps)
16532 {
16533 #if ATTENTION
16534     if (!cps->useSigint) return;
16535     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16536     switch (gameMode) {
16537       case MachinePlaysWhite:
16538       case MachinePlaysBlack:
16539       case TwoMachinesPlay:
16540       case IcsPlayingWhite:
16541       case IcsPlayingBlack:
16542       case AnalyzeMode:
16543       case AnalyzeFile:
16544         /* Skip if we know it isn't thinking */
16545         if (!cps->maybeThinking) return;
16546         if (appData.debugMode)
16547           fprintf(debugFP, "Interrupting %s\n", cps->which);
16548         InterruptChildProcess(cps->pr);
16549         cps->maybeThinking = FALSE;
16550         break;
16551       default:
16552         break;
16553     }
16554 #endif /*ATTENTION*/
16555 }
16556
16557 int
16558 CheckFlags ()
16559 {
16560     if (whiteTimeRemaining <= 0) {
16561         if (!whiteFlag) {
16562             whiteFlag = TRUE;
16563             if (appData.icsActive) {
16564                 if (appData.autoCallFlag &&
16565                     gameMode == IcsPlayingBlack && !blackFlag) {
16566                   SendToICS(ics_prefix);
16567                   SendToICS("flag\n");
16568                 }
16569             } else {
16570                 if (blackFlag) {
16571                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16572                 } else {
16573                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16574                     if (appData.autoCallFlag) {
16575                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16576                         return TRUE;
16577                     }
16578                 }
16579             }
16580         }
16581     }
16582     if (blackTimeRemaining <= 0) {
16583         if (!blackFlag) {
16584             blackFlag = TRUE;
16585             if (appData.icsActive) {
16586                 if (appData.autoCallFlag &&
16587                     gameMode == IcsPlayingWhite && !whiteFlag) {
16588                   SendToICS(ics_prefix);
16589                   SendToICS("flag\n");
16590                 }
16591             } else {
16592                 if (whiteFlag) {
16593                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16594                 } else {
16595                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16596                     if (appData.autoCallFlag) {
16597                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16598                         return TRUE;
16599                     }
16600                 }
16601             }
16602         }
16603     }
16604     return FALSE;
16605 }
16606
16607 void
16608 CheckTimeControl ()
16609 {
16610     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16611         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16612
16613     /*
16614      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16615      */
16616     if ( !WhiteOnMove(forwardMostMove) ) {
16617         /* White made time control */
16618         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16619         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16620         /* [HGM] time odds: correct new time quota for time odds! */
16621                                             / WhitePlayer()->timeOdds;
16622         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16623     } else {
16624         lastBlack -= blackTimeRemaining;
16625         /* Black made time control */
16626         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16627                                             / WhitePlayer()->other->timeOdds;
16628         lastWhite = whiteTimeRemaining;
16629     }
16630 }
16631
16632 void
16633 DisplayBothClocks ()
16634 {
16635     int wom = gameMode == EditPosition ?
16636       !blackPlaysFirst : WhiteOnMove(currentMove);
16637     DisplayWhiteClock(whiteTimeRemaining, wom);
16638     DisplayBlackClock(blackTimeRemaining, !wom);
16639 }
16640
16641
16642 /* Timekeeping seems to be a portability nightmare.  I think everyone
16643    has ftime(), but I'm really not sure, so I'm including some ifdefs
16644    to use other calls if you don't.  Clocks will be less accurate if
16645    you have neither ftime nor gettimeofday.
16646 */
16647
16648 /* VS 2008 requires the #include outside of the function */
16649 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16650 #include <sys/timeb.h>
16651 #endif
16652
16653 /* Get the current time as a TimeMark */
16654 void
16655 GetTimeMark (TimeMark *tm)
16656 {
16657 #if HAVE_GETTIMEOFDAY
16658
16659     struct timeval timeVal;
16660     struct timezone timeZone;
16661
16662     gettimeofday(&timeVal, &timeZone);
16663     tm->sec = (long) timeVal.tv_sec;
16664     tm->ms = (int) (timeVal.tv_usec / 1000L);
16665
16666 #else /*!HAVE_GETTIMEOFDAY*/
16667 #if HAVE_FTIME
16668
16669 // include <sys/timeb.h> / moved to just above start of function
16670     struct timeb timeB;
16671
16672     ftime(&timeB);
16673     tm->sec = (long) timeB.time;
16674     tm->ms = (int) timeB.millitm;
16675
16676 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16677     tm->sec = (long) time(NULL);
16678     tm->ms = 0;
16679 #endif
16680 #endif
16681 }
16682
16683 /* Return the difference in milliseconds between two
16684    time marks.  We assume the difference will fit in a long!
16685 */
16686 long
16687 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16688 {
16689     return 1000L*(tm2->sec - tm1->sec) +
16690            (long) (tm2->ms - tm1->ms);
16691 }
16692
16693
16694 /*
16695  * Code to manage the game clocks.
16696  *
16697  * In tournament play, black starts the clock and then white makes a move.
16698  * We give the human user a slight advantage if he is playing white---the
16699  * clocks don't run until he makes his first move, so it takes zero time.
16700  * Also, we don't account for network lag, so we could get out of sync
16701  * with GNU Chess's clock -- but then, referees are always right.
16702  */
16703
16704 static TimeMark tickStartTM;
16705 static long intendedTickLength;
16706
16707 long
16708 NextTickLength (long timeRemaining)
16709 {
16710     long nominalTickLength, nextTickLength;
16711
16712     if (timeRemaining > 0L && timeRemaining <= 10000L)
16713       nominalTickLength = 100L;
16714     else
16715       nominalTickLength = 1000L;
16716     nextTickLength = timeRemaining % nominalTickLength;
16717     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16718
16719     return nextTickLength;
16720 }
16721
16722 /* Adjust clock one minute up or down */
16723 void
16724 AdjustClock (Boolean which, int dir)
16725 {
16726     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16727     if(which) blackTimeRemaining += 60000*dir;
16728     else      whiteTimeRemaining += 60000*dir;
16729     DisplayBothClocks();
16730     adjustedClock = TRUE;
16731 }
16732
16733 /* Stop clocks and reset to a fresh time control */
16734 void
16735 ResetClocks ()
16736 {
16737     (void) StopClockTimer();
16738     if (appData.icsActive) {
16739         whiteTimeRemaining = blackTimeRemaining = 0;
16740     } else if (searchTime) {
16741         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16742         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16743     } else { /* [HGM] correct new time quote for time odds */
16744         whiteTC = blackTC = fullTimeControlString;
16745         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16746         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16747     }
16748     if (whiteFlag || blackFlag) {
16749         DisplayTitle("");
16750         whiteFlag = blackFlag = FALSE;
16751     }
16752     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16753     DisplayBothClocks();
16754     adjustedClock = FALSE;
16755 }
16756
16757 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16758
16759 /* Decrement running clock by amount of time that has passed */
16760 void
16761 DecrementClocks ()
16762 {
16763     long timeRemaining;
16764     long lastTickLength, fudge;
16765     TimeMark now;
16766
16767     if (!appData.clockMode) return;
16768     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16769
16770     GetTimeMark(&now);
16771
16772     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16773
16774     /* Fudge if we woke up a little too soon */
16775     fudge = intendedTickLength - lastTickLength;
16776     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16777
16778     if (WhiteOnMove(forwardMostMove)) {
16779         if(whiteNPS >= 0) lastTickLength = 0;
16780         timeRemaining = whiteTimeRemaining -= lastTickLength;
16781         if(timeRemaining < 0 && !appData.icsActive) {
16782             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16783             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16784                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16785                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16786             }
16787         }
16788         DisplayWhiteClock(whiteTimeRemaining - fudge,
16789                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16790     } else {
16791         if(blackNPS >= 0) lastTickLength = 0;
16792         timeRemaining = blackTimeRemaining -= lastTickLength;
16793         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16794             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16795             if(suddenDeath) {
16796                 blackStartMove = forwardMostMove;
16797                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16798             }
16799         }
16800         DisplayBlackClock(blackTimeRemaining - fudge,
16801                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16802     }
16803     if (CheckFlags()) return;
16804
16805     if(twoBoards) { // count down secondary board's clocks as well
16806         activePartnerTime -= lastTickLength;
16807         partnerUp = 1;
16808         if(activePartner == 'W')
16809             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16810         else
16811             DisplayBlackClock(activePartnerTime, TRUE);
16812         partnerUp = 0;
16813     }
16814
16815     tickStartTM = now;
16816     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16817     StartClockTimer(intendedTickLength);
16818
16819     /* if the time remaining has fallen below the alarm threshold, sound the
16820      * alarm. if the alarm has sounded and (due to a takeback or time control
16821      * with increment) the time remaining has increased to a level above the
16822      * threshold, reset the alarm so it can sound again.
16823      */
16824
16825     if (appData.icsActive && appData.icsAlarm) {
16826
16827         /* make sure we are dealing with the user's clock */
16828         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16829                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16830            )) return;
16831
16832         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16833             alarmSounded = FALSE;
16834         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16835             PlayAlarmSound();
16836             alarmSounded = TRUE;
16837         }
16838     }
16839 }
16840
16841
16842 /* A player has just moved, so stop the previously running
16843    clock and (if in clock mode) start the other one.
16844    We redisplay both clocks in case we're in ICS mode, because
16845    ICS gives us an update to both clocks after every move.
16846    Note that this routine is called *after* forwardMostMove
16847    is updated, so the last fractional tick must be subtracted
16848    from the color that is *not* on move now.
16849 */
16850 void
16851 SwitchClocks (int newMoveNr)
16852 {
16853     long lastTickLength;
16854     TimeMark now;
16855     int flagged = FALSE;
16856
16857     GetTimeMark(&now);
16858
16859     if (StopClockTimer() && appData.clockMode) {
16860         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16861         if (!WhiteOnMove(forwardMostMove)) {
16862             if(blackNPS >= 0) lastTickLength = 0;
16863             blackTimeRemaining -= lastTickLength;
16864            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16865 //         if(pvInfoList[forwardMostMove].time == -1)
16866                  pvInfoList[forwardMostMove].time =               // use GUI time
16867                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16868         } else {
16869            if(whiteNPS >= 0) lastTickLength = 0;
16870            whiteTimeRemaining -= lastTickLength;
16871            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16872 //         if(pvInfoList[forwardMostMove].time == -1)
16873                  pvInfoList[forwardMostMove].time =
16874                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16875         }
16876         flagged = CheckFlags();
16877     }
16878     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16879     CheckTimeControl();
16880
16881     if (flagged || !appData.clockMode) return;
16882
16883     switch (gameMode) {
16884       case MachinePlaysBlack:
16885       case MachinePlaysWhite:
16886       case BeginningOfGame:
16887         if (pausing) return;
16888         break;
16889
16890       case EditGame:
16891       case PlayFromGameFile:
16892       case IcsExamining:
16893         return;
16894
16895       default:
16896         break;
16897     }
16898
16899     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16900         if(WhiteOnMove(forwardMostMove))
16901              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16902         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16903     }
16904
16905     tickStartTM = now;
16906     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16907       whiteTimeRemaining : blackTimeRemaining);
16908     StartClockTimer(intendedTickLength);
16909 }
16910
16911
16912 /* Stop both clocks */
16913 void
16914 StopClocks ()
16915 {
16916     long lastTickLength;
16917     TimeMark now;
16918
16919     if (!StopClockTimer()) return;
16920     if (!appData.clockMode) return;
16921
16922     GetTimeMark(&now);
16923
16924     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16925     if (WhiteOnMove(forwardMostMove)) {
16926         if(whiteNPS >= 0) lastTickLength = 0;
16927         whiteTimeRemaining -= lastTickLength;
16928         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16929     } else {
16930         if(blackNPS >= 0) lastTickLength = 0;
16931         blackTimeRemaining -= lastTickLength;
16932         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16933     }
16934     CheckFlags();
16935 }
16936
16937 /* Start clock of player on move.  Time may have been reset, so
16938    if clock is already running, stop and restart it. */
16939 void
16940 StartClocks ()
16941 {
16942     (void) StopClockTimer(); /* in case it was running already */
16943     DisplayBothClocks();
16944     if (CheckFlags()) return;
16945
16946     if (!appData.clockMode) return;
16947     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16948
16949     GetTimeMark(&tickStartTM);
16950     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16951       whiteTimeRemaining : blackTimeRemaining);
16952
16953    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16954     whiteNPS = blackNPS = -1;
16955     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16956        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16957         whiteNPS = first.nps;
16958     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16959        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16960         blackNPS = first.nps;
16961     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16962         whiteNPS = second.nps;
16963     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16964         blackNPS = second.nps;
16965     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16966
16967     StartClockTimer(intendedTickLength);
16968 }
16969
16970 char *
16971 TimeString (long ms)
16972 {
16973     long second, minute, hour, day;
16974     char *sign = "";
16975     static char buf[32];
16976
16977     if (ms > 0 && ms <= 9900) {
16978       /* convert milliseconds to tenths, rounding up */
16979       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16980
16981       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16982       return buf;
16983     }
16984
16985     /* convert milliseconds to seconds, rounding up */
16986     /* use floating point to avoid strangeness of integer division
16987        with negative dividends on many machines */
16988     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16989
16990     if (second < 0) {
16991         sign = "-";
16992         second = -second;
16993     }
16994
16995     day = second / (60 * 60 * 24);
16996     second = second % (60 * 60 * 24);
16997     hour = second / (60 * 60);
16998     second = second % (60 * 60);
16999     minute = second / 60;
17000     second = second % 60;
17001
17002     if (day > 0)
17003       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17004               sign, day, hour, minute, second);
17005     else if (hour > 0)
17006       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17007     else
17008       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17009
17010     return buf;
17011 }
17012
17013
17014 /*
17015  * This is necessary because some C libraries aren't ANSI C compliant yet.
17016  */
17017 char *
17018 StrStr (char *string, char *match)
17019 {
17020     int i, length;
17021
17022     length = strlen(match);
17023
17024     for (i = strlen(string) - length; i >= 0; i--, string++)
17025       if (!strncmp(match, string, length))
17026         return string;
17027
17028     return NULL;
17029 }
17030
17031 char *
17032 StrCaseStr (char *string, char *match)
17033 {
17034     int i, j, length;
17035
17036     length = strlen(match);
17037
17038     for (i = strlen(string) - length; i >= 0; i--, string++) {
17039         for (j = 0; j < length; j++) {
17040             if (ToLower(match[j]) != ToLower(string[j]))
17041               break;
17042         }
17043         if (j == length) return string;
17044     }
17045
17046     return NULL;
17047 }
17048
17049 #ifndef _amigados
17050 int
17051 StrCaseCmp (char *s1, char *s2)
17052 {
17053     char c1, c2;
17054
17055     for (;;) {
17056         c1 = ToLower(*s1++);
17057         c2 = ToLower(*s2++);
17058         if (c1 > c2) return 1;
17059         if (c1 < c2) return -1;
17060         if (c1 == NULLCHAR) return 0;
17061     }
17062 }
17063
17064
17065 int
17066 ToLower (int c)
17067 {
17068     return isupper(c) ? tolower(c) : c;
17069 }
17070
17071
17072 int
17073 ToUpper (int c)
17074 {
17075     return islower(c) ? toupper(c) : c;
17076 }
17077 #endif /* !_amigados    */
17078
17079 char *
17080 StrSave (char *s)
17081 {
17082   char *ret;
17083
17084   if ((ret = (char *) malloc(strlen(s) + 1)))
17085     {
17086       safeStrCpy(ret, s, strlen(s)+1);
17087     }
17088   return ret;
17089 }
17090
17091 char *
17092 StrSavePtr (char *s, char **savePtr)
17093 {
17094     if (*savePtr) {
17095         free(*savePtr);
17096     }
17097     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17098       safeStrCpy(*savePtr, s, strlen(s)+1);
17099     }
17100     return(*savePtr);
17101 }
17102
17103 char *
17104 PGNDate ()
17105 {
17106     time_t clock;
17107     struct tm *tm;
17108     char buf[MSG_SIZ];
17109
17110     clock = time((time_t *)NULL);
17111     tm = localtime(&clock);
17112     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17113             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17114     return StrSave(buf);
17115 }
17116
17117
17118 char *
17119 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17120 {
17121     int i, j, fromX, fromY, toX, toY;
17122     int whiteToPlay;
17123     char buf[MSG_SIZ];
17124     char *p, *q;
17125     int emptycount;
17126     ChessSquare piece;
17127
17128     whiteToPlay = (gameMode == EditPosition) ?
17129       !blackPlaysFirst : (move % 2 == 0);
17130     p = buf;
17131
17132     /* Piece placement data */
17133     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17134         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17135         emptycount = 0;
17136         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17137             if (boards[move][i][j] == EmptySquare) {
17138                 emptycount++;
17139             } else { ChessSquare piece = boards[move][i][j];
17140                 if (emptycount > 0) {
17141                     if(emptycount<10) /* [HGM] can be >= 10 */
17142                         *p++ = '0' + emptycount;
17143                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17144                     emptycount = 0;
17145                 }
17146                 if(PieceToChar(piece) == '+') {
17147                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17148                     *p++ = '+';
17149                     piece = (ChessSquare)(DEMOTED piece);
17150                 }
17151                 *p++ = PieceToChar(piece);
17152                 if(p[-1] == '~') {
17153                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17154                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17155                     *p++ = '~';
17156                 }
17157             }
17158         }
17159         if (emptycount > 0) {
17160             if(emptycount<10) /* [HGM] can be >= 10 */
17161                 *p++ = '0' + emptycount;
17162             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17163             emptycount = 0;
17164         }
17165         *p++ = '/';
17166     }
17167     *(p - 1) = ' ';
17168
17169     /* [HGM] print Crazyhouse or Shogi holdings */
17170     if( gameInfo.holdingsWidth ) {
17171         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17172         q = p;
17173         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17174             piece = boards[move][i][BOARD_WIDTH-1];
17175             if( piece != EmptySquare )
17176               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17177                   *p++ = PieceToChar(piece);
17178         }
17179         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17180             piece = boards[move][BOARD_HEIGHT-i-1][0];
17181             if( piece != EmptySquare )
17182               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17183                   *p++ = PieceToChar(piece);
17184         }
17185
17186         if( q == p ) *p++ = '-';
17187         *p++ = ']';
17188         *p++ = ' ';
17189     }
17190
17191     /* Active color */
17192     *p++ = whiteToPlay ? 'w' : 'b';
17193     *p++ = ' ';
17194
17195   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17196     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17197   } else {
17198   if(nrCastlingRights) {
17199      q = p;
17200      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17201        /* [HGM] write directly from rights */
17202            if(boards[move][CASTLING][2] != NoRights &&
17203               boards[move][CASTLING][0] != NoRights   )
17204                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17205            if(boards[move][CASTLING][2] != NoRights &&
17206               boards[move][CASTLING][1] != NoRights   )
17207                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17208            if(boards[move][CASTLING][5] != NoRights &&
17209               boards[move][CASTLING][3] != NoRights   )
17210                 *p++ = boards[move][CASTLING][3] + AAA;
17211            if(boards[move][CASTLING][5] != NoRights &&
17212               boards[move][CASTLING][4] != NoRights   )
17213                 *p++ = boards[move][CASTLING][4] + AAA;
17214      } else {
17215
17216         /* [HGM] write true castling rights */
17217         if( nrCastlingRights == 6 ) {
17218             int q, k=0;
17219             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17220                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17221             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17222                  boards[move][CASTLING][2] != NoRights  );
17223             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17224                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17225                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17226                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17227                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17228             }
17229             if(q) *p++ = 'Q';
17230             k = 0;
17231             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17232                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17233             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17234                  boards[move][CASTLING][5] != NoRights  );
17235             if(gameInfo.variant == VariantSChess) {
17236                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17237                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17238                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17239                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17240             }
17241             if(q) *p++ = 'q';
17242         }
17243      }
17244      if (q == p) *p++ = '-'; /* No castling rights */
17245      *p++ = ' ';
17246   }
17247
17248   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17249      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17250      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17251     /* En passant target square */
17252     if (move > backwardMostMove) {
17253         fromX = moveList[move - 1][0] - AAA;
17254         fromY = moveList[move - 1][1] - ONE;
17255         toX = moveList[move - 1][2] - AAA;
17256         toY = moveList[move - 1][3] - ONE;
17257         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17258             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17259             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17260             fromX == toX) {
17261             /* 2-square pawn move just happened */
17262             *p++ = toX + AAA;
17263             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17264         } else {
17265             *p++ = '-';
17266         }
17267     } else if(move == backwardMostMove) {
17268         // [HGM] perhaps we should always do it like this, and forget the above?
17269         if((signed char)boards[move][EP_STATUS] >= 0) {
17270             *p++ = boards[move][EP_STATUS] + AAA;
17271             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17272         } else {
17273             *p++ = '-';
17274         }
17275     } else {
17276         *p++ = '-';
17277     }
17278     *p++ = ' ';
17279   }
17280   }
17281
17282     if(moveCounts)
17283     {   int i = 0, j=move;
17284
17285         /* [HGM] find reversible plies */
17286         if (appData.debugMode) { int k;
17287             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17288             for(k=backwardMostMove; k<=forwardMostMove; k++)
17289                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17290
17291         }
17292
17293         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17294         if( j == backwardMostMove ) i += initialRulePlies;
17295         sprintf(p, "%d ", i);
17296         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17297
17298         /* Fullmove number */
17299         sprintf(p, "%d", (move / 2) + 1);
17300     } else *--p = NULLCHAR;
17301
17302     return StrSave(buf);
17303 }
17304
17305 Boolean
17306 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17307 {
17308     int i, j;
17309     char *p, c;
17310     int emptycount, virgin[BOARD_FILES];
17311     ChessSquare piece;
17312
17313     p = fen;
17314
17315     /* [HGM] by default clear Crazyhouse holdings, if present */
17316     if(gameInfo.holdingsWidth) {
17317        for(i=0; i<BOARD_HEIGHT; i++) {
17318            board[i][0]             = EmptySquare; /* black holdings */
17319            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17320            board[i][1]             = (ChessSquare) 0; /* black counts */
17321            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17322        }
17323     }
17324
17325     /* Piece placement data */
17326     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17327         j = 0;
17328         for (;;) {
17329             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17330                 if (*p == '/') p++;
17331                 emptycount = gameInfo.boardWidth - j;
17332                 while (emptycount--)
17333                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17334                 break;
17335 #if(BOARD_FILES >= 10)
17336             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17337                 p++; emptycount=10;
17338                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17339                 while (emptycount--)
17340                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17341 #endif
17342             } else if (isdigit(*p)) {
17343                 emptycount = *p++ - '0';
17344                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17345                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17346                 while (emptycount--)
17347                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17348             } else if (*p == '+' || isalpha(*p)) {
17349                 if (j >= gameInfo.boardWidth) return FALSE;
17350                 if(*p=='+') {
17351                     piece = CharToPiece(*++p);
17352                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17353                     piece = (ChessSquare) (PROMOTED piece ); p++;
17354                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17355                 } else piece = CharToPiece(*p++);
17356
17357                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17358                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17359                     piece = (ChessSquare) (PROMOTED piece);
17360                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17361                     p++;
17362                 }
17363                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17364             } else {
17365                 return FALSE;
17366             }
17367         }
17368     }
17369     while (*p == '/' || *p == ' ') p++;
17370
17371     /* [HGM] look for Crazyhouse holdings here */
17372     while(*p==' ') p++;
17373     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17374         if(*p == '[') p++;
17375         if(*p == '-' ) p++; /* empty holdings */ else {
17376             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17377             /* if we would allow FEN reading to set board size, we would   */
17378             /* have to add holdings and shift the board read so far here   */
17379             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17380                 p++;
17381                 if((int) piece >= (int) BlackPawn ) {
17382                     i = (int)piece - (int)BlackPawn;
17383                     i = PieceToNumber((ChessSquare)i);
17384                     if( i >= gameInfo.holdingsSize ) return FALSE;
17385                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17386                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17387                 } else {
17388                     i = (int)piece - (int)WhitePawn;
17389                     i = PieceToNumber((ChessSquare)i);
17390                     if( i >= gameInfo.holdingsSize ) return FALSE;
17391                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17392                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17393                 }
17394             }
17395         }
17396         if(*p == ']') p++;
17397     }
17398
17399     while(*p == ' ') p++;
17400
17401     /* Active color */
17402     c = *p++;
17403     if(appData.colorNickNames) {
17404       if( c == appData.colorNickNames[0] ) c = 'w'; else
17405       if( c == appData.colorNickNames[1] ) c = 'b';
17406     }
17407     switch (c) {
17408       case 'w':
17409         *blackPlaysFirst = FALSE;
17410         break;
17411       case 'b':
17412         *blackPlaysFirst = TRUE;
17413         break;
17414       default:
17415         return FALSE;
17416     }
17417
17418     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17419     /* return the extra info in global variiables             */
17420
17421     /* set defaults in case FEN is incomplete */
17422     board[EP_STATUS] = EP_UNKNOWN;
17423     for(i=0; i<nrCastlingRights; i++ ) {
17424         board[CASTLING][i] =
17425             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17426     }   /* assume possible unless obviously impossible */
17427     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17428     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17429     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17430                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17431     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17432     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17433     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17434                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17435     FENrulePlies = 0;
17436
17437     while(*p==' ') p++;
17438     if(nrCastlingRights) {
17439       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17440       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17441           /* castling indicator present, so default becomes no castlings */
17442           for(i=0; i<nrCastlingRights; i++ ) {
17443                  board[CASTLING][i] = NoRights;
17444           }
17445       }
17446       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17447              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17448              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17449              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17450         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17451
17452         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17453             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17454             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17455         }
17456         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17457             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17458         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17459                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17460         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17461                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17462         switch(c) {
17463           case'K':
17464               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17465               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17466               board[CASTLING][2] = whiteKingFile;
17467               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17468               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17469               break;
17470           case'Q':
17471               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17472               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17473               board[CASTLING][2] = whiteKingFile;
17474               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17475               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17476               break;
17477           case'k':
17478               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17479               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17480               board[CASTLING][5] = blackKingFile;
17481               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17482               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17483               break;
17484           case'q':
17485               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17486               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17487               board[CASTLING][5] = blackKingFile;
17488               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17489               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17490           case '-':
17491               break;
17492           default: /* FRC castlings */
17493               if(c >= 'a') { /* black rights */
17494                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17495                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17496                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17497                   if(i == BOARD_RGHT) break;
17498                   board[CASTLING][5] = i;
17499                   c -= AAA;
17500                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17501                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17502                   if(c > i)
17503                       board[CASTLING][3] = c;
17504                   else
17505                       board[CASTLING][4] = c;
17506               } else { /* white rights */
17507                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17508                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17509                     if(board[0][i] == WhiteKing) break;
17510                   if(i == BOARD_RGHT) break;
17511                   board[CASTLING][2] = i;
17512                   c -= AAA - 'a' + 'A';
17513                   if(board[0][c] >= WhiteKing) break;
17514                   if(c > i)
17515                       board[CASTLING][0] = c;
17516                   else
17517                       board[CASTLING][1] = c;
17518               }
17519         }
17520       }
17521       for(i=0; i<nrCastlingRights; i++)
17522         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17523       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17524     if (appData.debugMode) {
17525         fprintf(debugFP, "FEN castling rights:");
17526         for(i=0; i<nrCastlingRights; i++)
17527         fprintf(debugFP, " %d", board[CASTLING][i]);
17528         fprintf(debugFP, "\n");
17529     }
17530
17531       while(*p==' ') p++;
17532     }
17533
17534     /* read e.p. field in games that know e.p. capture */
17535     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17536        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17537        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17538       if(*p=='-') {
17539         p++; board[EP_STATUS] = EP_NONE;
17540       } else {
17541          char c = *p++ - AAA;
17542
17543          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17544          if(*p >= '0' && *p <='9') p++;
17545          board[EP_STATUS] = c;
17546       }
17547     }
17548
17549
17550     if(sscanf(p, "%d", &i) == 1) {
17551         FENrulePlies = i; /* 50-move ply counter */
17552         /* (The move number is still ignored)    */
17553     }
17554
17555     return TRUE;
17556 }
17557
17558 void
17559 EditPositionPasteFEN (char *fen)
17560 {
17561   if (fen != NULL) {
17562     Board initial_position;
17563
17564     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17565       DisplayError(_("Bad FEN position in clipboard"), 0);
17566       return ;
17567     } else {
17568       int savedBlackPlaysFirst = blackPlaysFirst;
17569       EditPositionEvent();
17570       blackPlaysFirst = savedBlackPlaysFirst;
17571       CopyBoard(boards[0], initial_position);
17572       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17573       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17574       DisplayBothClocks();
17575       DrawPosition(FALSE, boards[currentMove]);
17576     }
17577   }
17578 }
17579
17580 static char cseq[12] = "\\   ";
17581
17582 Boolean
17583 set_cont_sequence (char *new_seq)
17584 {
17585     int len;
17586     Boolean ret;
17587
17588     // handle bad attempts to set the sequence
17589         if (!new_seq)
17590                 return 0; // acceptable error - no debug
17591
17592     len = strlen(new_seq);
17593     ret = (len > 0) && (len < sizeof(cseq));
17594     if (ret)
17595       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17596     else if (appData.debugMode)
17597       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17598     return ret;
17599 }
17600
17601 /*
17602     reformat a source message so words don't cross the width boundary.  internal
17603     newlines are not removed.  returns the wrapped size (no null character unless
17604     included in source message).  If dest is NULL, only calculate the size required
17605     for the dest buffer.  lp argument indicats line position upon entry, and it's
17606     passed back upon exit.
17607 */
17608 int
17609 wrap (char *dest, char *src, int count, int width, int *lp)
17610 {
17611     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17612
17613     cseq_len = strlen(cseq);
17614     old_line = line = *lp;
17615     ansi = len = clen = 0;
17616
17617     for (i=0; i < count; i++)
17618     {
17619         if (src[i] == '\033')
17620             ansi = 1;
17621
17622         // if we hit the width, back up
17623         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17624         {
17625             // store i & len in case the word is too long
17626             old_i = i, old_len = len;
17627
17628             // find the end of the last word
17629             while (i && src[i] != ' ' && src[i] != '\n')
17630             {
17631                 i--;
17632                 len--;
17633             }
17634
17635             // word too long?  restore i & len before splitting it
17636             if ((old_i-i+clen) >= width)
17637             {
17638                 i = old_i;
17639                 len = old_len;
17640             }
17641
17642             // extra space?
17643             if (i && src[i-1] == ' ')
17644                 len--;
17645
17646             if (src[i] != ' ' && src[i] != '\n')
17647             {
17648                 i--;
17649                 if (len)
17650                     len--;
17651             }
17652
17653             // now append the newline and continuation sequence
17654             if (dest)
17655                 dest[len] = '\n';
17656             len++;
17657             if (dest)
17658                 strncpy(dest+len, cseq, cseq_len);
17659             len += cseq_len;
17660             line = cseq_len;
17661             clen = cseq_len;
17662             continue;
17663         }
17664
17665         if (dest)
17666             dest[len] = src[i];
17667         len++;
17668         if (!ansi)
17669             line++;
17670         if (src[i] == '\n')
17671             line = 0;
17672         if (src[i] == 'm')
17673             ansi = 0;
17674     }
17675     if (dest && appData.debugMode)
17676     {
17677         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17678             count, width, line, len, *lp);
17679         show_bytes(debugFP, src, count);
17680         fprintf(debugFP, "\ndest: ");
17681         show_bytes(debugFP, dest, len);
17682         fprintf(debugFP, "\n");
17683     }
17684     *lp = dest ? line : old_line;
17685
17686     return len;
17687 }
17688
17689 // [HGM] vari: routines for shelving variations
17690 Boolean modeRestore = FALSE;
17691
17692 void
17693 PushInner (int firstMove, int lastMove)
17694 {
17695         int i, j, nrMoves = lastMove - firstMove;
17696
17697         // push current tail of game on stack
17698         savedResult[storedGames] = gameInfo.result;
17699         savedDetails[storedGames] = gameInfo.resultDetails;
17700         gameInfo.resultDetails = NULL;
17701         savedFirst[storedGames] = firstMove;
17702         savedLast [storedGames] = lastMove;
17703         savedFramePtr[storedGames] = framePtr;
17704         framePtr -= nrMoves; // reserve space for the boards
17705         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17706             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17707             for(j=0; j<MOVE_LEN; j++)
17708                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17709             for(j=0; j<2*MOVE_LEN; j++)
17710                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17711             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17712             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17713             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17714             pvInfoList[firstMove+i-1].depth = 0;
17715             commentList[framePtr+i] = commentList[firstMove+i];
17716             commentList[firstMove+i] = NULL;
17717         }
17718
17719         storedGames++;
17720         forwardMostMove = firstMove; // truncate game so we can start variation
17721 }
17722
17723 void
17724 PushTail (int firstMove, int lastMove)
17725 {
17726         if(appData.icsActive) { // only in local mode
17727                 forwardMostMove = currentMove; // mimic old ICS behavior
17728                 return;
17729         }
17730         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17731
17732         PushInner(firstMove, lastMove);
17733         if(storedGames == 1) GreyRevert(FALSE);
17734         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17735 }
17736
17737 void
17738 PopInner (Boolean annotate)
17739 {
17740         int i, j, nrMoves;
17741         char buf[8000], moveBuf[20];
17742
17743         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17744         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17745         nrMoves = savedLast[storedGames] - currentMove;
17746         if(annotate) {
17747                 int cnt = 10;
17748                 if(!WhiteOnMove(currentMove))
17749                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17750                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17751                 for(i=currentMove; i<forwardMostMove; i++) {
17752                         if(WhiteOnMove(i))
17753                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17754                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17755                         strcat(buf, moveBuf);
17756                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17757                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17758                 }
17759                 strcat(buf, ")");
17760         }
17761         for(i=1; i<=nrMoves; i++) { // copy last variation back
17762             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17763             for(j=0; j<MOVE_LEN; j++)
17764                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17765             for(j=0; j<2*MOVE_LEN; j++)
17766                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17767             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17768             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17769             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17770             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17771             commentList[currentMove+i] = commentList[framePtr+i];
17772             commentList[framePtr+i] = NULL;
17773         }
17774         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17775         framePtr = savedFramePtr[storedGames];
17776         gameInfo.result = savedResult[storedGames];
17777         if(gameInfo.resultDetails != NULL) {
17778             free(gameInfo.resultDetails);
17779       }
17780         gameInfo.resultDetails = savedDetails[storedGames];
17781         forwardMostMove = currentMove + nrMoves;
17782 }
17783
17784 Boolean
17785 PopTail (Boolean annotate)
17786 {
17787         if(appData.icsActive) return FALSE; // only in local mode
17788         if(!storedGames) return FALSE; // sanity
17789         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17790
17791         PopInner(annotate);
17792         if(currentMove < forwardMostMove) ForwardEvent(); else
17793         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17794
17795         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17796         return TRUE;
17797 }
17798
17799 void
17800 CleanupTail ()
17801 {       // remove all shelved variations
17802         int i;
17803         for(i=0; i<storedGames; i++) {
17804             if(savedDetails[i])
17805                 free(savedDetails[i]);
17806             savedDetails[i] = NULL;
17807         }
17808         for(i=framePtr; i<MAX_MOVES; i++) {
17809                 if(commentList[i]) free(commentList[i]);
17810                 commentList[i] = NULL;
17811         }
17812         framePtr = MAX_MOVES-1;
17813         storedGames = 0;
17814 }
17815
17816 void
17817 LoadVariation (int index, char *text)
17818 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17819         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17820         int level = 0, move;
17821
17822         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17823         // first find outermost bracketing variation
17824         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17825             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17826                 if(*p == '{') wait = '}'; else
17827                 if(*p == '[') wait = ']'; else
17828                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17829                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17830             }
17831             if(*p == wait) wait = NULLCHAR; // closing ]} found
17832             p++;
17833         }
17834         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17835         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17836         end[1] = NULLCHAR; // clip off comment beyond variation
17837         ToNrEvent(currentMove-1);
17838         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17839         // kludge: use ParsePV() to append variation to game
17840         move = currentMove;
17841         ParsePV(start, TRUE, TRUE);
17842         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17843         ClearPremoveHighlights();
17844         CommentPopDown();
17845         ToNrEvent(currentMove+1);
17846 }
17847
17848 void
17849 LoadTheme ()
17850 {
17851     char *p, *q, buf[MSG_SIZ];
17852     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17853         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17854         ParseArgsFromString(buf);
17855         ActivateTheme(TRUE); // also redo colors
17856         return;
17857     }
17858     p = nickName;
17859     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17860     {
17861         int len;
17862         q = appData.themeNames;
17863         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17864       if(appData.useBitmaps) {
17865         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17866                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17867                 appData.liteBackTextureMode,
17868                 appData.darkBackTextureMode );
17869       } else {
17870         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17871                 Col2Text(2),   // lightSquareColor
17872                 Col2Text(3) ); // darkSquareColor
17873       }
17874       if(appData.useBorder) {
17875         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17876                 appData.border);
17877       } else {
17878         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17879       }
17880       if(appData.useFont) {
17881         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17882                 appData.renderPiecesWithFont,
17883                 appData.fontToPieceTable,
17884                 Col2Text(9),    // appData.fontBackColorWhite
17885                 Col2Text(10) ); // appData.fontForeColorBlack
17886       } else {
17887         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17888                 appData.pieceDirectory);
17889         if(!appData.pieceDirectory[0])
17890           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17891                 Col2Text(0),   // whitePieceColor
17892                 Col2Text(1) ); // blackPieceColor
17893       }
17894       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17895                 Col2Text(4),   // highlightSquareColor
17896                 Col2Text(5) ); // premoveHighlightColor
17897         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17898         if(insert != q) insert[-1] = NULLCHAR;
17899         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17900         if(q)   free(q);
17901     }
17902     ActivateTheme(FALSE);
17903 }