b7c32469bbd20dc263b48ac9ef6c5322e7fe2b77
[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((VariantClass v, int w, int h, int s));
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 static int initPing = -1;
279
280 /* States for ics_getting_history */
281 #define H_FALSE 0
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
287
288 /* whosays values for GameEnds */
289 #define GE_ICS 0
290 #define GE_ENGINE 1
291 #define GE_PLAYER 2
292 #define GE_FILE 3
293 #define GE_XBOARD 4
294 #define GE_ENGINE1 5
295 #define GE_ENGINE2 6
296
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
299
300 /* Different types of move when calling RegisterMove */
301 #define CMAIL_MOVE   0
302 #define CMAIL_RESIGN 1
303 #define CMAIL_DRAW   2
304 #define CMAIL_ACCEPT 3
305
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
310
311 /* Telnet protocol constants */
312 #define TN_WILL 0373
313 #define TN_WONT 0374
314 #define TN_DO   0375
315 #define TN_DONT 0376
316 #define TN_IAC  0377
317 #define TN_ECHO 0001
318 #define TN_SGA  0003
319 #define TN_PORT 23
320
321 char*
322 safeStrCpy (char *dst, const char *src, size_t count)
323 { // [HGM] made safe
324   int i;
325   assert( dst != NULL );
326   assert( src != NULL );
327   assert( count > 0 );
328
329   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330   if(  i == count && dst[count-1] != NULLCHAR)
331     {
332       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333       if(appData.debugMode)
334         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335     }
336
337   return dst;
338 }
339
340 /* Some compiler can't cast u64 to double
341  * This function do the job for us:
342
343  * We use the highest bit for cast, this only
344  * works if the highest bit is not
345  * in use (This should not happen)
346  *
347  * We used this for all compiler
348  */
349 double
350 u64ToDouble (u64 value)
351 {
352   double r;
353   u64 tmp = value & u64Const(0x7fffffffffffffff);
354   r = (double)(s64)tmp;
355   if (value & u64Const(0x8000000000000000))
356        r +=  9.2233720368547758080e18; /* 2^63 */
357  return r;
358 }
359
360 /* Fake up flags for now, as we aren't keeping track of castling
361    availability yet. [HGM] Change of logic: the flag now only
362    indicates the type of castlings allowed by the rule of the game.
363    The actual rights themselves are maintained in the array
364    castlingRights, as part of the game history, and are not probed
365    by this function.
366  */
367 int
368 PosFlags (index)
369 {
370   int flags = F_ALL_CASTLE_OK;
371   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372   switch (gameInfo.variant) {
373   case VariantSuicide:
374     flags &= ~F_ALL_CASTLE_OK;
375   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376     flags |= F_IGNORE_CHECK;
377   case VariantLosers:
378     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379     break;
380   case VariantAtomic:
381     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382     break;
383   case VariantKriegspiel:
384     flags |= F_KRIEGSPIEL_CAPTURE;
385     break;
386   case VariantCapaRandom:
387   case VariantFischeRandom:
388     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389   case VariantNoCastle:
390   case VariantShatranj:
391   case VariantCourier:
392   case VariantMakruk:
393   case VariantASEAN:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second, pairing;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackMan, BlackFerz,
567         BlackKing, BlackMan, BlackKnight, BlackRook }
568 };
569
570 ChessSquare  lionArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackRook, BlackLion, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackKnight, BlackRook }
575 };
576
577
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
584 };
585
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
591 };
592
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
598 };
599
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
605 };
606
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
612 };
613
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
619 };
620
621 ChessSquare ChuChessArray[2][BOARD_FILES] = {
622     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
623         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
624     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
625         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
626 };
627
628 #ifdef GOTHIC
629 ChessSquare GothicArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
631         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
633         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
634 };
635 #else // !GOTHIC
636 #define GothicArray CapablancaArray
637 #endif // !GOTHIC
638
639 #ifdef FALCON
640 ChessSquare FalconArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
642         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
644         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
645 };
646 #else // !FALCON
647 #define FalconArray CapablancaArray
648 #endif // !FALCON
649
650 #else // !(BOARD_FILES>=10)
651 #define XiangqiPosition FIDEArray
652 #define CapablancaArray FIDEArray
653 #define GothicArray FIDEArray
654 #define GreatArray FIDEArray
655 #endif // !(BOARD_FILES>=10)
656
657 #if (BOARD_FILES>=12)
658 ChessSquare CourierArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
660         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
662         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
663 };
664 ChessSquare ChuArray[6][BOARD_FILES] = {
665     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
666       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
667     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
668       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
669     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
670       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
671     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
672       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
673     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
674       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
675     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
676       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
677 };
678 #else // !(BOARD_FILES>=12)
679 #define CourierArray CapablancaArray
680 #define ChuArray CapablancaArray
681 #endif // !(BOARD_FILES>=12)
682
683
684 Board initialPosition;
685
686
687 /* Convert str to a rating. Checks for special cases of "----",
688
689    "++++", etc. Also strips ()'s */
690 int
691 string_to_rating (char *str)
692 {
693   while(*str && !isdigit(*str)) ++str;
694   if (!*str)
695     return 0;   /* One of the special "no rating" cases */
696   else
697     return atoi(str);
698 }
699
700 void
701 ClearProgramStats ()
702 {
703     /* Init programStats */
704     programStats.movelist[0] = 0;
705     programStats.depth = 0;
706     programStats.nr_moves = 0;
707     programStats.moves_left = 0;
708     programStats.nodes = 0;
709     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
710     programStats.score = 0;
711     programStats.got_only_move = 0;
712     programStats.got_fail = 0;
713     programStats.line_is_book = 0;
714 }
715
716 void
717 CommonEngineInit ()
718 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
719     if (appData.firstPlaysBlack) {
720         first.twoMachinesColor = "black\n";
721         second.twoMachinesColor = "white\n";
722     } else {
723         first.twoMachinesColor = "white\n";
724         second.twoMachinesColor = "black\n";
725     }
726
727     first.other = &second;
728     second.other = &first;
729
730     { float norm = 1;
731         if(appData.timeOddsMode) {
732             norm = appData.timeOdds[0];
733             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
734         }
735         first.timeOdds  = appData.timeOdds[0]/norm;
736         second.timeOdds = appData.timeOdds[1]/norm;
737     }
738
739     if(programVersion) free(programVersion);
740     if (appData.noChessProgram) {
741         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
742         sprintf(programVersion, "%s", PACKAGE_STRING);
743     } else {
744       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
745       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
746       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
747     }
748 }
749
750 void
751 UnloadEngine (ChessProgramState *cps)
752 {
753         /* Kill off first chess program */
754         if (cps->isr != NULL)
755           RemoveInputSource(cps->isr);
756         cps->isr = NULL;
757
758         if (cps->pr != NoProc) {
759             ExitAnalyzeMode();
760             DoSleep( appData.delayBeforeQuit );
761             SendToProgram("quit\n", cps);
762             DoSleep( appData.delayAfterQuit );
763             DestroyChildProcess(cps->pr, cps->useSigterm);
764         }
765         cps->pr = NoProc;
766         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
767 }
768
769 void
770 ClearOptions (ChessProgramState *cps)
771 {
772     int i;
773     cps->nrOptions = cps->comboCnt = 0;
774     for(i=0; i<MAX_OPTIONS; i++) {
775         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
776         cps->option[i].textValue = 0;
777     }
778 }
779
780 char *engineNames[] = {
781   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
782      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
783 N_("first"),
784   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
785      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
786 N_("second")
787 };
788
789 void
790 InitEngine (ChessProgramState *cps, int n)
791 {   // [HGM] all engine initialiation put in a function that does one engine
792
793     ClearOptions(cps);
794
795     cps->which = engineNames[n];
796     cps->maybeThinking = FALSE;
797     cps->pr = NoProc;
798     cps->isr = NULL;
799     cps->sendTime = 2;
800     cps->sendDrawOffers = 1;
801
802     cps->program = appData.chessProgram[n];
803     cps->host = appData.host[n];
804     cps->dir = appData.directory[n];
805     cps->initString = appData.engInitString[n];
806     cps->computerString = appData.computerString[n];
807     cps->useSigint  = TRUE;
808     cps->useSigterm = TRUE;
809     cps->reuse = appData.reuse[n];
810     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
811     cps->useSetboard = FALSE;
812     cps->useSAN = FALSE;
813     cps->usePing = FALSE;
814     cps->lastPing = 0;
815     cps->lastPong = 0;
816     cps->usePlayother = FALSE;
817     cps->useColors = TRUE;
818     cps->useUsermove = FALSE;
819     cps->sendICS = FALSE;
820     cps->sendName = appData.icsActive;
821     cps->sdKludge = FALSE;
822     cps->stKludge = FALSE;
823     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
824     TidyProgramName(cps->program, cps->host, cps->tidy);
825     cps->matchWins = 0;
826     ASSIGN(cps->variants, appData.variant);
827     cps->analysisSupport = 2; /* detect */
828     cps->analyzing = FALSE;
829     cps->initDone = FALSE;
830     cps->reload = FALSE;
831
832     /* New features added by Tord: */
833     cps->useFEN960 = FALSE;
834     cps->useOOCastle = TRUE;
835     /* End of new features added by Tord. */
836     cps->fenOverride  = appData.fenOverride[n];
837
838     /* [HGM] time odds: set factor for each machine */
839     cps->timeOdds  = appData.timeOdds[n];
840
841     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
842     cps->accumulateTC = appData.accumulateTC[n];
843     cps->maxNrOfSessions = 1;
844
845     /* [HGM] debug */
846     cps->debug = FALSE;
847
848     cps->supportsNPS = UNKNOWN;
849     cps->memSize = FALSE;
850     cps->maxCores = FALSE;
851     ASSIGN(cps->egtFormats, "");
852
853     /* [HGM] options */
854     cps->optionSettings  = appData.engOptions[n];
855
856     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
857     cps->isUCI = appData.isUCI[n]; /* [AS] */
858     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
859     cps->highlight = 0;
860
861     if (appData.protocolVersion[n] > PROTOVER
862         || appData.protocolVersion[n] < 1)
863       {
864         char buf[MSG_SIZ];
865         int len;
866
867         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
868                        appData.protocolVersion[n]);
869         if( (len >= MSG_SIZ) && appData.debugMode )
870           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
871
872         DisplayFatalError(buf, 0, 2);
873       }
874     else
875       {
876         cps->protocolVersion = appData.protocolVersion[n];
877       }
878
879     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
880     ParseFeatures(appData.featureDefaults, cps);
881 }
882
883 ChessProgramState *savCps;
884
885 GameMode oldMode;
886
887 void
888 LoadEngine ()
889 {
890     int i;
891     if(WaitForEngine(savCps, LoadEngine)) return;
892     CommonEngineInit(); // recalculate time odds
893     if(gameInfo.variant != StringToVariant(appData.variant)) {
894         // we changed variant when loading the engine; this forces us to reset
895         Reset(TRUE, savCps != &first);
896         oldMode = BeginningOfGame; // to prevent restoring old mode
897     }
898     InitChessProgram(savCps, FALSE);
899     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
900     DisplayMessage("", "");
901     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
902     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
903     ThawUI();
904     SetGNUMode();
905     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
906 }
907
908 void
909 ReplaceEngine (ChessProgramState *cps, int n)
910 {
911     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
912     keepInfo = 1;
913     if(oldMode != BeginningOfGame) EditGameEvent();
914     keepInfo = 0;
915     UnloadEngine(cps);
916     appData.noChessProgram = FALSE;
917     appData.clockMode = TRUE;
918     InitEngine(cps, n);
919     UpdateLogos(TRUE);
920     if(n) return; // only startup first engine immediately; second can wait
921     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
922     LoadEngine();
923 }
924
925 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
926 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
927
928 static char resetOptions[] =
929         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
930         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
931         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
932         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
933
934 void
935 FloatToFront(char **list, char *engineLine)
936 {
937     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
938     int i=0;
939     if(appData.recentEngines <= 0) return;
940     TidyProgramName(engineLine, "localhost", tidy+1);
941     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
942     strncpy(buf+1, *list, MSG_SIZ-50);
943     if(p = strstr(buf, tidy)) { // tidy name appears in list
944         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
945         while(*p++ = *++q); // squeeze out
946     }
947     strcat(tidy, buf+1); // put list behind tidy name
948     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
949     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
950     ASSIGN(*list, tidy+1);
951 }
952
953 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
954
955 void
956 Load (ChessProgramState *cps, int i)
957 {
958     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
959     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
960         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
961         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
962         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
963         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
964         appData.firstProtocolVersion = PROTOVER;
965         ParseArgsFromString(buf);
966         SwapEngines(i);
967         ReplaceEngine(cps, i);
968         FloatToFront(&appData.recentEngineList, engineLine);
969         return;
970     }
971     p = engineName;
972     while(q = strchr(p, SLASH)) p = q+1;
973     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
974     if(engineDir[0] != NULLCHAR) {
975         ASSIGN(appData.directory[i], engineDir); p = engineName;
976     } else if(p != engineName) { // derive directory from engine path, when not given
977         p[-1] = 0;
978         ASSIGN(appData.directory[i], engineName);
979         p[-1] = SLASH;
980         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
981     } else { ASSIGN(appData.directory[i], "."); }
982     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
983     if(params[0]) {
984         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
985         snprintf(command, MSG_SIZ, "%s %s", p, params);
986         p = command;
987     }
988     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
989     ASSIGN(appData.chessProgram[i], p);
990     appData.isUCI[i] = isUCI;
991     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
992     appData.hasOwnBookUCI[i] = hasBook;
993     if(!nickName[0]) useNick = FALSE;
994     if(useNick) ASSIGN(appData.pgnName[i], nickName);
995     if(addToList) {
996         int len;
997         char quote;
998         q = firstChessProgramNames;
999         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1000         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1001         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1002                         quote, p, quote, appData.directory[i],
1003                         useNick ? " -fn \"" : "",
1004                         useNick ? nickName : "",
1005                         useNick ? "\"" : "",
1006                         v1 ? " -firstProtocolVersion 1" : "",
1007                         hasBook ? "" : " -fNoOwnBookUCI",
1008                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1009                         storeVariant ? " -variant " : "",
1010                         storeVariant ? VariantName(gameInfo.variant) : "");
1011         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1012         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1013         if(insert != q) insert[-1] = NULLCHAR;
1014         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1015         if(q)   free(q);
1016         FloatToFront(&appData.recentEngineList, buf);
1017     }
1018     ReplaceEngine(cps, i);
1019 }
1020
1021 void
1022 InitTimeControls ()
1023 {
1024     int matched, min, sec;
1025     /*
1026      * Parse timeControl resource
1027      */
1028     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1029                           appData.movesPerSession)) {
1030         char buf[MSG_SIZ];
1031         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1032         DisplayFatalError(buf, 0, 2);
1033     }
1034
1035     /*
1036      * Parse searchTime resource
1037      */
1038     if (*appData.searchTime != NULLCHAR) {
1039         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1040         if (matched == 1) {
1041             searchTime = min * 60;
1042         } else if (matched == 2) {
1043             searchTime = min * 60 + sec;
1044         } else {
1045             char buf[MSG_SIZ];
1046             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1047             DisplayFatalError(buf, 0, 2);
1048         }
1049     }
1050 }
1051
1052 void
1053 InitBackEnd1 ()
1054 {
1055
1056     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1057     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1058
1059     GetTimeMark(&programStartTime);
1060     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1061     appData.seedBase = random() + (random()<<15);
1062     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1063
1064     ClearProgramStats();
1065     programStats.ok_to_send = 1;
1066     programStats.seen_stat = 0;
1067
1068     /*
1069      * Initialize game list
1070      */
1071     ListNew(&gameList);
1072
1073
1074     /*
1075      * Internet chess server status
1076      */
1077     if (appData.icsActive) {
1078         appData.matchMode = FALSE;
1079         appData.matchGames = 0;
1080 #if ZIPPY
1081         appData.noChessProgram = !appData.zippyPlay;
1082 #else
1083         appData.zippyPlay = FALSE;
1084         appData.zippyTalk = FALSE;
1085         appData.noChessProgram = TRUE;
1086 #endif
1087         if (*appData.icsHelper != NULLCHAR) {
1088             appData.useTelnet = TRUE;
1089             appData.telnetProgram = appData.icsHelper;
1090         }
1091     } else {
1092         appData.zippyTalk = appData.zippyPlay = FALSE;
1093     }
1094
1095     /* [AS] Initialize pv info list [HGM] and game state */
1096     {
1097         int i, j;
1098
1099         for( i=0; i<=framePtr; i++ ) {
1100             pvInfoList[i].depth = -1;
1101             boards[i][EP_STATUS] = EP_NONE;
1102             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1103         }
1104     }
1105
1106     InitTimeControls();
1107
1108     /* [AS] Adjudication threshold */
1109     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1110
1111     InitEngine(&first, 0);
1112     InitEngine(&second, 1);
1113     CommonEngineInit();
1114
1115     pairing.which = "pairing"; // pairing engine
1116     pairing.pr = NoProc;
1117     pairing.isr = NULL;
1118     pairing.program = appData.pairingEngine;
1119     pairing.host = "localhost";
1120     pairing.dir = ".";
1121
1122     if (appData.icsActive) {
1123         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1124     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1125         appData.clockMode = FALSE;
1126         first.sendTime = second.sendTime = 0;
1127     }
1128
1129 #if ZIPPY
1130     /* Override some settings from environment variables, for backward
1131        compatibility.  Unfortunately it's not feasible to have the env
1132        vars just set defaults, at least in xboard.  Ugh.
1133     */
1134     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1135       ZippyInit();
1136     }
1137 #endif
1138
1139     if (!appData.icsActive) {
1140       char buf[MSG_SIZ];
1141       int len;
1142
1143       /* Check for variants that are supported only in ICS mode,
1144          or not at all.  Some that are accepted here nevertheless
1145          have bugs; see comments below.
1146       */
1147       VariantClass variant = StringToVariant(appData.variant);
1148       switch (variant) {
1149       case VariantBughouse:     /* need four players and two boards */
1150       case VariantKriegspiel:   /* need to hide pieces and move details */
1151         /* case VariantFischeRandom: (Fabien: moved below) */
1152         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1153         if( (len >= MSG_SIZ) && appData.debugMode )
1154           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1155
1156         DisplayFatalError(buf, 0, 2);
1157         return;
1158
1159       case VariantUnknown:
1160       case VariantLoadable:
1161       case Variant29:
1162       case Variant30:
1163       case Variant31:
1164       case Variant32:
1165       case Variant33:
1166       case Variant34:
1167       case Variant35:
1168       case Variant36:
1169       default:
1170         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1171         if( (len >= MSG_SIZ) && appData.debugMode )
1172           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1173
1174         DisplayFatalError(buf, 0, 2);
1175         return;
1176
1177       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1178       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1179       case VariantGothic:     /* [HGM] should work */
1180       case VariantCapablanca: /* [HGM] should work */
1181       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1182       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1183       case VariantChu:        /* [HGM] experimental */
1184       case VariantKnightmate: /* [HGM] should work */
1185       case VariantCylinder:   /* [HGM] untested */
1186       case VariantFalcon:     /* [HGM] untested */
1187       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1188                                  offboard interposition not understood */
1189       case VariantNormal:     /* definitely works! */
1190       case VariantWildCastle: /* pieces not automatically shuffled */
1191       case VariantNoCastle:   /* pieces not automatically shuffled */
1192       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1193       case VariantLosers:     /* should work except for win condition,
1194                                  and doesn't know captures are mandatory */
1195       case VariantSuicide:    /* should work except for win condition,
1196                                  and doesn't know captures are mandatory */
1197       case VariantGiveaway:   /* should work except for win condition,
1198                                  and doesn't know captures are mandatory */
1199       case VariantTwoKings:   /* should work */
1200       case VariantAtomic:     /* should work except for win condition */
1201       case Variant3Check:     /* should work except for win condition */
1202       case VariantShatranj:   /* should work except for all win conditions */
1203       case VariantMakruk:     /* should work except for draw countdown */
1204       case VariantASEAN :     /* should work except for draw countdown */
1205       case VariantBerolina:   /* might work if TestLegality is off */
1206       case VariantCapaRandom: /* should work */
1207       case VariantJanus:      /* should work */
1208       case VariantSuper:      /* experimental */
1209       case VariantGreat:      /* experimental, requires legality testing to be off */
1210       case VariantSChess:     /* S-Chess, should work */
1211       case VariantGrand:      /* should work */
1212       case VariantSpartan:    /* should work */
1213       case VariantLion:       /* should work */
1214       case VariantChuChess:   /* should work */
1215         break;
1216       }
1217     }
1218
1219 }
1220
1221 int
1222 NextIntegerFromString (char ** str, long * value)
1223 {
1224     int result = -1;
1225     char * s = *str;
1226
1227     while( *s == ' ' || *s == '\t' ) {
1228         s++;
1229     }
1230
1231     *value = 0;
1232
1233     if( *s >= '0' && *s <= '9' ) {
1234         while( *s >= '0' && *s <= '9' ) {
1235             *value = *value * 10 + (*s - '0');
1236             s++;
1237         }
1238
1239         result = 0;
1240     }
1241
1242     *str = s;
1243
1244     return result;
1245 }
1246
1247 int
1248 NextTimeControlFromString (char ** str, long * value)
1249 {
1250     long temp;
1251     int result = NextIntegerFromString( str, &temp );
1252
1253     if( result == 0 ) {
1254         *value = temp * 60; /* Minutes */
1255         if( **str == ':' ) {
1256             (*str)++;
1257             result = NextIntegerFromString( str, &temp );
1258             *value += temp; /* Seconds */
1259         }
1260     }
1261
1262     return result;
1263 }
1264
1265 int
1266 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1267 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1268     int result = -1, type = 0; long temp, temp2;
1269
1270     if(**str != ':') return -1; // old params remain in force!
1271     (*str)++;
1272     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1273     if( NextIntegerFromString( str, &temp ) ) return -1;
1274     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1275
1276     if(**str != '/') {
1277         /* time only: incremental or sudden-death time control */
1278         if(**str == '+') { /* increment follows; read it */
1279             (*str)++;
1280             if(**str == '!') type = *(*str)++; // Bronstein TC
1281             if(result = NextIntegerFromString( str, &temp2)) return -1;
1282             *inc = temp2 * 1000;
1283             if(**str == '.') { // read fraction of increment
1284                 char *start = ++(*str);
1285                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1286                 temp2 *= 1000;
1287                 while(start++ < *str) temp2 /= 10;
1288                 *inc += temp2;
1289             }
1290         } else *inc = 0;
1291         *moves = 0; *tc = temp * 1000; *incType = type;
1292         return 0;
1293     }
1294
1295     (*str)++; /* classical time control */
1296     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1297
1298     if(result == 0) {
1299         *moves = temp;
1300         *tc    = temp2 * 1000;
1301         *inc   = 0;
1302         *incType = type;
1303     }
1304     return result;
1305 }
1306
1307 int
1308 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1309 {   /* [HGM] get time to add from the multi-session time-control string */
1310     int incType, moves=1; /* kludge to force reading of first session */
1311     long time, increment;
1312     char *s = tcString;
1313
1314     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1315     do {
1316         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1317         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1318         if(movenr == -1) return time;    /* last move before new session     */
1319         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1320         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1321         if(!moves) return increment;     /* current session is incremental   */
1322         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1323     } while(movenr >= -1);               /* try again for next session       */
1324
1325     return 0; // no new time quota on this move
1326 }
1327
1328 int
1329 ParseTimeControl (char *tc, float ti, int mps)
1330 {
1331   long tc1;
1332   long tc2;
1333   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1334   int min, sec=0;
1335
1336   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1337   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1338       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1339   if(ti > 0) {
1340
1341     if(mps)
1342       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1343     else
1344       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1345   } else {
1346     if(mps)
1347       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1348     else
1349       snprintf(buf, MSG_SIZ, ":%s", mytc);
1350   }
1351   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1352
1353   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1354     return FALSE;
1355   }
1356
1357   if( *tc == '/' ) {
1358     /* Parse second time control */
1359     tc++;
1360
1361     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1362       return FALSE;
1363     }
1364
1365     if( tc2 == 0 ) {
1366       return FALSE;
1367     }
1368
1369     timeControl_2 = tc2 * 1000;
1370   }
1371   else {
1372     timeControl_2 = 0;
1373   }
1374
1375   if( tc1 == 0 ) {
1376     return FALSE;
1377   }
1378
1379   timeControl = tc1 * 1000;
1380
1381   if (ti >= 0) {
1382     timeIncrement = ti * 1000;  /* convert to ms */
1383     movesPerSession = 0;
1384   } else {
1385     timeIncrement = 0;
1386     movesPerSession = mps;
1387   }
1388   return TRUE;
1389 }
1390
1391 void
1392 InitBackEnd2 ()
1393 {
1394     if (appData.debugMode) {
1395 #    ifdef __GIT_VERSION
1396       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1397 #    else
1398       fprintf(debugFP, "Version: %s\n", programVersion);
1399 #    endif
1400     }
1401     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1402
1403     set_cont_sequence(appData.wrapContSeq);
1404     if (appData.matchGames > 0) {
1405         appData.matchMode = TRUE;
1406     } else if (appData.matchMode) {
1407         appData.matchGames = 1;
1408     }
1409     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1410         appData.matchGames = appData.sameColorGames;
1411     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1412         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1413         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1414     }
1415     Reset(TRUE, FALSE);
1416     if (appData.noChessProgram || first.protocolVersion == 1) {
1417       InitBackEnd3();
1418     } else {
1419       /* kludge: allow timeout for initial "feature" commands */
1420       FreezeUI();
1421       DisplayMessage("", _("Starting chess program"));
1422       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1423     }
1424 }
1425
1426 int
1427 CalculateIndex (int index, int gameNr)
1428 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1429     int res;
1430     if(index > 0) return index; // fixed nmber
1431     if(index == 0) return 1;
1432     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1433     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1434     return res;
1435 }
1436
1437 int
1438 LoadGameOrPosition (int gameNr)
1439 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1440     if (*appData.loadGameFile != NULLCHAR) {
1441         if (!LoadGameFromFile(appData.loadGameFile,
1442                 CalculateIndex(appData.loadGameIndex, gameNr),
1443                               appData.loadGameFile, FALSE)) {
1444             DisplayFatalError(_("Bad game file"), 0, 1);
1445             return 0;
1446         }
1447     } else if (*appData.loadPositionFile != NULLCHAR) {
1448         if (!LoadPositionFromFile(appData.loadPositionFile,
1449                 CalculateIndex(appData.loadPositionIndex, gameNr),
1450                                   appData.loadPositionFile)) {
1451             DisplayFatalError(_("Bad position file"), 0, 1);
1452             return 0;
1453         }
1454     }
1455     return 1;
1456 }
1457
1458 void
1459 ReserveGame (int gameNr, char resChar)
1460 {
1461     FILE *tf = fopen(appData.tourneyFile, "r+");
1462     char *p, *q, c, buf[MSG_SIZ];
1463     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1464     safeStrCpy(buf, lastMsg, MSG_SIZ);
1465     DisplayMessage(_("Pick new game"), "");
1466     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1467     ParseArgsFromFile(tf);
1468     p = q = appData.results;
1469     if(appData.debugMode) {
1470       char *r = appData.participants;
1471       fprintf(debugFP, "results = '%s'\n", p);
1472       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1473       fprintf(debugFP, "\n");
1474     }
1475     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1476     nextGame = q - p;
1477     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1478     safeStrCpy(q, p, strlen(p) + 2);
1479     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1480     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1481     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1482         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1483         q[nextGame] = '*';
1484     }
1485     fseek(tf, -(strlen(p)+4), SEEK_END);
1486     c = fgetc(tf);
1487     if(c != '"') // depending on DOS or Unix line endings we can be one off
1488          fseek(tf, -(strlen(p)+2), SEEK_END);
1489     else fseek(tf, -(strlen(p)+3), SEEK_END);
1490     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1491     DisplayMessage(buf, "");
1492     free(p); appData.results = q;
1493     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1494        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1495       int round = appData.defaultMatchGames * appData.tourneyType;
1496       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1497          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1498         UnloadEngine(&first);  // next game belongs to other pairing;
1499         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1500     }
1501     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1502 }
1503
1504 void
1505 MatchEvent (int mode)
1506 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1507         int dummy;
1508         if(matchMode) { // already in match mode: switch it off
1509             abortMatch = TRUE;
1510             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1511             return;
1512         }
1513 //      if(gameMode != BeginningOfGame) {
1514 //          DisplayError(_("You can only start a match from the initial position."), 0);
1515 //          return;
1516 //      }
1517         abortMatch = FALSE;
1518         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1519         /* Set up machine vs. machine match */
1520         nextGame = 0;
1521         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1522         if(appData.tourneyFile[0]) {
1523             ReserveGame(-1, 0);
1524             if(nextGame > appData.matchGames) {
1525                 char buf[MSG_SIZ];
1526                 if(strchr(appData.results, '*') == NULL) {
1527                     FILE *f;
1528                     appData.tourneyCycles++;
1529                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1530                         fclose(f);
1531                         NextTourneyGame(-1, &dummy);
1532                         ReserveGame(-1, 0);
1533                         if(nextGame <= appData.matchGames) {
1534                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1535                             matchMode = mode;
1536                             ScheduleDelayedEvent(NextMatchGame, 10000);
1537                             return;
1538                         }
1539                     }
1540                 }
1541                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1542                 DisplayError(buf, 0);
1543                 appData.tourneyFile[0] = 0;
1544                 return;
1545             }
1546         } else
1547         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1548             DisplayFatalError(_("Can't have a match with no chess programs"),
1549                               0, 2);
1550             return;
1551         }
1552         matchMode = mode;
1553         matchGame = roundNr = 1;
1554         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1555         NextMatchGame();
1556 }
1557
1558 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1559
1560 void
1561 InitBackEnd3 P((void))
1562 {
1563     GameMode initialMode;
1564     char buf[MSG_SIZ];
1565     int err, len;
1566
1567     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1568        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1569         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1570        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1571        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1572         char c, *q = first.variants, *p = strchr(q, ',');
1573         if(p) *p = NULLCHAR;
1574         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1575             int w, h, s;
1576             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1577                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1578             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1579             Reset(TRUE, FALSE);         // and re-initialize
1580         }
1581         if(p) *p = ',';
1582     }
1583
1584     InitChessProgram(&first, startedFromSetupPosition);
1585
1586     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1587         free(programVersion);
1588         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1589         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1590         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1591     }
1592
1593     if (appData.icsActive) {
1594 #ifdef WIN32
1595         /* [DM] Make a console window if needed [HGM] merged ifs */
1596         ConsoleCreate();
1597 #endif
1598         err = establish();
1599         if (err != 0)
1600           {
1601             if (*appData.icsCommPort != NULLCHAR)
1602               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1603                              appData.icsCommPort);
1604             else
1605               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1606                         appData.icsHost, appData.icsPort);
1607
1608             if( (len >= MSG_SIZ) && appData.debugMode )
1609               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1610
1611             DisplayFatalError(buf, err, 1);
1612             return;
1613         }
1614         SetICSMode();
1615         telnetISR =
1616           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1617         fromUserISR =
1618           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1619         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1620             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1621     } else if (appData.noChessProgram) {
1622         SetNCPMode();
1623     } else {
1624         SetGNUMode();
1625     }
1626
1627     if (*appData.cmailGameName != NULLCHAR) {
1628         SetCmailMode();
1629         OpenLoopback(&cmailPR);
1630         cmailISR =
1631           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1632     }
1633
1634     ThawUI();
1635     DisplayMessage("", "");
1636     if (StrCaseCmp(appData.initialMode, "") == 0) {
1637       initialMode = BeginningOfGame;
1638       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1639         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1640         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1641         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1642         ModeHighlight();
1643       }
1644     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1645       initialMode = TwoMachinesPlay;
1646     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1647       initialMode = AnalyzeFile;
1648     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1649       initialMode = AnalyzeMode;
1650     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1651       initialMode = MachinePlaysWhite;
1652     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1653       initialMode = MachinePlaysBlack;
1654     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1655       initialMode = EditGame;
1656     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1657       initialMode = EditPosition;
1658     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1659       initialMode = Training;
1660     } else {
1661       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1662       if( (len >= MSG_SIZ) && appData.debugMode )
1663         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1664
1665       DisplayFatalError(buf, 0, 2);
1666       return;
1667     }
1668
1669     if (appData.matchMode) {
1670         if(appData.tourneyFile[0]) { // start tourney from command line
1671             FILE *f;
1672             if(f = fopen(appData.tourneyFile, "r")) {
1673                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1674                 fclose(f);
1675                 appData.clockMode = TRUE;
1676                 SetGNUMode();
1677             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1678         }
1679         MatchEvent(TRUE);
1680     } else if (*appData.cmailGameName != NULLCHAR) {
1681         /* Set up cmail mode */
1682         ReloadCmailMsgEvent(TRUE);
1683     } else {
1684         /* Set up other modes */
1685         if (initialMode == AnalyzeFile) {
1686           if (*appData.loadGameFile == NULLCHAR) {
1687             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1688             return;
1689           }
1690         }
1691         if (*appData.loadGameFile != NULLCHAR) {
1692             (void) LoadGameFromFile(appData.loadGameFile,
1693                                     appData.loadGameIndex,
1694                                     appData.loadGameFile, TRUE);
1695         } else if (*appData.loadPositionFile != NULLCHAR) {
1696             (void) LoadPositionFromFile(appData.loadPositionFile,
1697                                         appData.loadPositionIndex,
1698                                         appData.loadPositionFile);
1699             /* [HGM] try to make self-starting even after FEN load */
1700             /* to allow automatic setup of fairy variants with wtm */
1701             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1702                 gameMode = BeginningOfGame;
1703                 setboardSpoiledMachineBlack = 1;
1704             }
1705             /* [HGM] loadPos: make that every new game uses the setup */
1706             /* from file as long as we do not switch variant          */
1707             if(!blackPlaysFirst) {
1708                 startedFromPositionFile = TRUE;
1709                 CopyBoard(filePosition, boards[0]);
1710             }
1711         }
1712         if (initialMode == AnalyzeMode) {
1713           if (appData.noChessProgram) {
1714             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1715             return;
1716           }
1717           if (appData.icsActive) {
1718             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1719             return;
1720           }
1721           AnalyzeModeEvent();
1722         } else if (initialMode == AnalyzeFile) {
1723           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1724           ShowThinkingEvent();
1725           AnalyzeFileEvent();
1726           AnalysisPeriodicEvent(1);
1727         } else if (initialMode == MachinePlaysWhite) {
1728           if (appData.noChessProgram) {
1729             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1730                               0, 2);
1731             return;
1732           }
1733           if (appData.icsActive) {
1734             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1735                               0, 2);
1736             return;
1737           }
1738           MachineWhiteEvent();
1739         } else if (initialMode == MachinePlaysBlack) {
1740           if (appData.noChessProgram) {
1741             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1742                               0, 2);
1743             return;
1744           }
1745           if (appData.icsActive) {
1746             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1747                               0, 2);
1748             return;
1749           }
1750           MachineBlackEvent();
1751         } else if (initialMode == TwoMachinesPlay) {
1752           if (appData.noChessProgram) {
1753             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1754                               0, 2);
1755             return;
1756           }
1757           if (appData.icsActive) {
1758             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1759                               0, 2);
1760             return;
1761           }
1762           TwoMachinesEvent();
1763         } else if (initialMode == EditGame) {
1764           EditGameEvent();
1765         } else if (initialMode == EditPosition) {
1766           EditPositionEvent();
1767         } else if (initialMode == Training) {
1768           if (*appData.loadGameFile == NULLCHAR) {
1769             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1770             return;
1771           }
1772           TrainingEvent();
1773         }
1774     }
1775 }
1776
1777 void
1778 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1779 {
1780     DisplayBook(current+1);
1781
1782     MoveHistorySet( movelist, first, last, current, pvInfoList );
1783
1784     EvalGraphSet( first, last, current, pvInfoList );
1785
1786     MakeEngineOutputTitle();
1787 }
1788
1789 /*
1790  * Establish will establish a contact to a remote host.port.
1791  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1792  *  used to talk to the host.
1793  * Returns 0 if okay, error code if not.
1794  */
1795 int
1796 establish ()
1797 {
1798     char buf[MSG_SIZ];
1799
1800     if (*appData.icsCommPort != NULLCHAR) {
1801         /* Talk to the host through a serial comm port */
1802         return OpenCommPort(appData.icsCommPort, &icsPR);
1803
1804     } else if (*appData.gateway != NULLCHAR) {
1805         if (*appData.remoteShell == NULLCHAR) {
1806             /* Use the rcmd protocol to run telnet program on a gateway host */
1807             snprintf(buf, sizeof(buf), "%s %s %s",
1808                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1809             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1810
1811         } else {
1812             /* Use the rsh program to run telnet program on a gateway host */
1813             if (*appData.remoteUser == NULLCHAR) {
1814                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1815                         appData.gateway, appData.telnetProgram,
1816                         appData.icsHost, appData.icsPort);
1817             } else {
1818                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1819                         appData.remoteShell, appData.gateway,
1820                         appData.remoteUser, appData.telnetProgram,
1821                         appData.icsHost, appData.icsPort);
1822             }
1823             return StartChildProcess(buf, "", &icsPR);
1824
1825         }
1826     } else if (appData.useTelnet) {
1827         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1828
1829     } else {
1830         /* TCP socket interface differs somewhat between
1831            Unix and NT; handle details in the front end.
1832            */
1833         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1834     }
1835 }
1836
1837 void
1838 EscapeExpand (char *p, char *q)
1839 {       // [HGM] initstring: routine to shape up string arguments
1840         while(*p++ = *q++) if(p[-1] == '\\')
1841             switch(*q++) {
1842                 case 'n': p[-1] = '\n'; break;
1843                 case 'r': p[-1] = '\r'; break;
1844                 case 't': p[-1] = '\t'; break;
1845                 case '\\': p[-1] = '\\'; break;
1846                 case 0: *p = 0; return;
1847                 default: p[-1] = q[-1]; break;
1848             }
1849 }
1850
1851 void
1852 show_bytes (FILE *fp, char *buf, int count)
1853 {
1854     while (count--) {
1855         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1856             fprintf(fp, "\\%03o", *buf & 0xff);
1857         } else {
1858             putc(*buf, fp);
1859         }
1860         buf++;
1861     }
1862     fflush(fp);
1863 }
1864
1865 /* Returns an errno value */
1866 int
1867 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1868 {
1869     char buf[8192], *p, *q, *buflim;
1870     int left, newcount, outcount;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1873         *appData.gateway != NULLCHAR) {
1874         if (appData.debugMode) {
1875             fprintf(debugFP, ">ICS: ");
1876             show_bytes(debugFP, message, count);
1877             fprintf(debugFP, "\n");
1878         }
1879         return OutputToProcess(pr, message, count, outError);
1880     }
1881
1882     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1883     p = message;
1884     q = buf;
1885     left = count;
1886     newcount = 0;
1887     while (left) {
1888         if (q >= buflim) {
1889             if (appData.debugMode) {
1890                 fprintf(debugFP, ">ICS: ");
1891                 show_bytes(debugFP, buf, newcount);
1892                 fprintf(debugFP, "\n");
1893             }
1894             outcount = OutputToProcess(pr, buf, newcount, outError);
1895             if (outcount < newcount) return -1; /* to be sure */
1896             q = buf;
1897             newcount = 0;
1898         }
1899         if (*p == '\n') {
1900             *q++ = '\r';
1901             newcount++;
1902         } else if (((unsigned char) *p) == TN_IAC) {
1903             *q++ = (char) TN_IAC;
1904             newcount ++;
1905         }
1906         *q++ = *p++;
1907         newcount++;
1908         left--;
1909     }
1910     if (appData.debugMode) {
1911         fprintf(debugFP, ">ICS: ");
1912         show_bytes(debugFP, buf, newcount);
1913         fprintf(debugFP, "\n");
1914     }
1915     outcount = OutputToProcess(pr, buf, newcount, outError);
1916     if (outcount < newcount) return -1; /* to be sure */
1917     return count;
1918 }
1919
1920 void
1921 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1922 {
1923     int outError, outCount;
1924     static int gotEof = 0;
1925     static FILE *ini;
1926
1927     /* Pass data read from player on to ICS */
1928     if (count > 0) {
1929         gotEof = 0;
1930         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1931         if (outCount < count) {
1932             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1933         }
1934         if(have_sent_ICS_logon == 2) {
1935           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1936             fprintf(ini, "%s", message);
1937             have_sent_ICS_logon = 3;
1938           } else
1939             have_sent_ICS_logon = 1;
1940         } else if(have_sent_ICS_logon == 3) {
1941             fprintf(ini, "%s", message);
1942             fclose(ini);
1943           have_sent_ICS_logon = 1;
1944         }
1945     } else if (count < 0) {
1946         RemoveInputSource(isr);
1947         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1948     } else if (gotEof++ > 0) {
1949         RemoveInputSource(isr);
1950         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1951     }
1952 }
1953
1954 void
1955 KeepAlive ()
1956 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1957     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1958     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1959     SendToICS("date\n");
1960     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1961 }
1962
1963 /* added routine for printf style output to ics */
1964 void
1965 ics_printf (char *format, ...)
1966 {
1967     char buffer[MSG_SIZ];
1968     va_list args;
1969
1970     va_start(args, format);
1971     vsnprintf(buffer, sizeof(buffer), format, args);
1972     buffer[sizeof(buffer)-1] = '\0';
1973     SendToICS(buffer);
1974     va_end(args);
1975 }
1976
1977 void
1978 SendToICS (char *s)
1979 {
1980     int count, outCount, outError;
1981
1982     if (icsPR == NoProc) return;
1983
1984     count = strlen(s);
1985     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1986     if (outCount < count) {
1987         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1988     }
1989 }
1990
1991 /* This is used for sending logon scripts to the ICS. Sending
1992    without a delay causes problems when using timestamp on ICC
1993    (at least on my machine). */
1994 void
1995 SendToICSDelayed (char *s, long msdelay)
1996 {
1997     int count, outCount, outError;
1998
1999     if (icsPR == NoProc) return;
2000
2001     count = strlen(s);
2002     if (appData.debugMode) {
2003         fprintf(debugFP, ">ICS: ");
2004         show_bytes(debugFP, s, count);
2005         fprintf(debugFP, "\n");
2006     }
2007     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2008                                       msdelay);
2009     if (outCount < count) {
2010         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2011     }
2012 }
2013
2014
2015 /* Remove all highlighting escape sequences in s
2016    Also deletes any suffix starting with '('
2017    */
2018 char *
2019 StripHighlightAndTitle (char *s)
2020 {
2021     static char retbuf[MSG_SIZ];
2022     char *p = retbuf;
2023
2024     while (*s != NULLCHAR) {
2025         while (*s == '\033') {
2026             while (*s != NULLCHAR && !isalpha(*s)) s++;
2027             if (*s != NULLCHAR) s++;
2028         }
2029         while (*s != NULLCHAR && *s != '\033') {
2030             if (*s == '(' || *s == '[') {
2031                 *p = NULLCHAR;
2032                 return retbuf;
2033             }
2034             *p++ = *s++;
2035         }
2036     }
2037     *p = NULLCHAR;
2038     return retbuf;
2039 }
2040
2041 /* Remove all highlighting escape sequences in s */
2042 char *
2043 StripHighlight (char *s)
2044 {
2045     static char retbuf[MSG_SIZ];
2046     char *p = retbuf;
2047
2048     while (*s != NULLCHAR) {
2049         while (*s == '\033') {
2050             while (*s != NULLCHAR && !isalpha(*s)) s++;
2051             if (*s != NULLCHAR) s++;
2052         }
2053         while (*s != NULLCHAR && *s != '\033') {
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 char engineVariant[MSG_SIZ];
2062 char *variantNames[] = VARIANT_NAMES;
2063 char *
2064 VariantName (VariantClass v)
2065 {
2066     if(v == VariantUnknown || *engineVariant) return engineVariant;
2067     return variantNames[v];
2068 }
2069
2070
2071 /* Identify a variant from the strings the chess servers use or the
2072    PGN Variant tag names we use. */
2073 VariantClass
2074 StringToVariant (char *e)
2075 {
2076     char *p;
2077     int wnum = -1;
2078     VariantClass v = VariantNormal;
2079     int i, found = FALSE;
2080     char buf[MSG_SIZ];
2081     int len;
2082
2083     if (!e) return v;
2084
2085     /* [HGM] skip over optional board-size prefixes */
2086     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2087         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2088         while( *e++ != '_');
2089     }
2090
2091     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2092         v = VariantNormal;
2093         found = TRUE;
2094     } else
2095     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2096       if (p = StrCaseStr(e, variantNames[i])) {
2097         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2098         v = (VariantClass) i;
2099         found = TRUE;
2100         break;
2101       }
2102     }
2103
2104     if (!found) {
2105       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2106           || StrCaseStr(e, "wild/fr")
2107           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2108         v = VariantFischeRandom;
2109       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2110                  (i = 1, p = StrCaseStr(e, "w"))) {
2111         p += i;
2112         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2113         if (isdigit(*p)) {
2114           wnum = atoi(p);
2115         } else {
2116           wnum = -1;
2117         }
2118         switch (wnum) {
2119         case 0: /* FICS only, actually */
2120         case 1:
2121           /* Castling legal even if K starts on d-file */
2122           v = VariantWildCastle;
2123           break;
2124         case 2:
2125         case 3:
2126         case 4:
2127           /* Castling illegal even if K & R happen to start in
2128              normal positions. */
2129           v = VariantNoCastle;
2130           break;
2131         case 5:
2132         case 7:
2133         case 8:
2134         case 10:
2135         case 11:
2136         case 12:
2137         case 13:
2138         case 14:
2139         case 15:
2140         case 18:
2141         case 19:
2142           /* Castling legal iff K & R start in normal positions */
2143           v = VariantNormal;
2144           break;
2145         case 6:
2146         case 20:
2147         case 21:
2148           /* Special wilds for position setup; unclear what to do here */
2149           v = VariantLoadable;
2150           break;
2151         case 9:
2152           /* Bizarre ICC game */
2153           v = VariantTwoKings;
2154           break;
2155         case 16:
2156           v = VariantKriegspiel;
2157           break;
2158         case 17:
2159           v = VariantLosers;
2160           break;
2161         case 22:
2162           v = VariantFischeRandom;
2163           break;
2164         case 23:
2165           v = VariantCrazyhouse;
2166           break;
2167         case 24:
2168           v = VariantBughouse;
2169           break;
2170         case 25:
2171           v = Variant3Check;
2172           break;
2173         case 26:
2174           /* Not quite the same as FICS suicide! */
2175           v = VariantGiveaway;
2176           break;
2177         case 27:
2178           v = VariantAtomic;
2179           break;
2180         case 28:
2181           v = VariantShatranj;
2182           break;
2183
2184         /* Temporary names for future ICC types.  The name *will* change in
2185            the next xboard/WinBoard release after ICC defines it. */
2186         case 29:
2187           v = Variant29;
2188           break;
2189         case 30:
2190           v = Variant30;
2191           break;
2192         case 31:
2193           v = Variant31;
2194           break;
2195         case 32:
2196           v = Variant32;
2197           break;
2198         case 33:
2199           v = Variant33;
2200           break;
2201         case 34:
2202           v = Variant34;
2203           break;
2204         case 35:
2205           v = Variant35;
2206           break;
2207         case 36:
2208           v = Variant36;
2209           break;
2210         case 37:
2211           v = VariantShogi;
2212           break;
2213         case 38:
2214           v = VariantXiangqi;
2215           break;
2216         case 39:
2217           v = VariantCourier;
2218           break;
2219         case 40:
2220           v = VariantGothic;
2221           break;
2222         case 41:
2223           v = VariantCapablanca;
2224           break;
2225         case 42:
2226           v = VariantKnightmate;
2227           break;
2228         case 43:
2229           v = VariantFairy;
2230           break;
2231         case 44:
2232           v = VariantCylinder;
2233           break;
2234         case 45:
2235           v = VariantFalcon;
2236           break;
2237         case 46:
2238           v = VariantCapaRandom;
2239           break;
2240         case 47:
2241           v = VariantBerolina;
2242           break;
2243         case 48:
2244           v = VariantJanus;
2245           break;
2246         case 49:
2247           v = VariantSuper;
2248           break;
2249         case 50:
2250           v = VariantGreat;
2251           break;
2252         case -1:
2253           /* Found "wild" or "w" in the string but no number;
2254              must assume it's normal chess. */
2255           v = VariantNormal;
2256           break;
2257         default:
2258           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2259           if( (len >= MSG_SIZ) && appData.debugMode )
2260             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2261
2262           DisplayError(buf, 0);
2263           v = VariantUnknown;
2264           break;
2265         }
2266       }
2267     }
2268     if (appData.debugMode) {
2269       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2270               e, wnum, VariantName(v));
2271     }
2272     return v;
2273 }
2274
2275 static int leftover_start = 0, leftover_len = 0;
2276 char star_match[STAR_MATCH_N][MSG_SIZ];
2277
2278 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2279    advance *index beyond it, and set leftover_start to the new value of
2280    *index; else return FALSE.  If pattern contains the character '*', it
2281    matches any sequence of characters not containing '\r', '\n', or the
2282    character following the '*' (if any), and the matched sequence(s) are
2283    copied into star_match.
2284    */
2285 int
2286 looking_at ( char *buf, int *index, char *pattern)
2287 {
2288     char *bufp = &buf[*index], *patternp = pattern;
2289     int star_count = 0;
2290     char *matchp = star_match[0];
2291
2292     for (;;) {
2293         if (*patternp == NULLCHAR) {
2294             *index = leftover_start = bufp - buf;
2295             *matchp = NULLCHAR;
2296             return TRUE;
2297         }
2298         if (*bufp == NULLCHAR) return FALSE;
2299         if (*patternp == '*') {
2300             if (*bufp == *(patternp + 1)) {
2301                 *matchp = NULLCHAR;
2302                 matchp = star_match[++star_count];
2303                 patternp += 2;
2304                 bufp++;
2305                 continue;
2306             } else if (*bufp == '\n' || *bufp == '\r') {
2307                 patternp++;
2308                 if (*patternp == NULLCHAR)
2309                   continue;
2310                 else
2311                   return FALSE;
2312             } else {
2313                 *matchp++ = *bufp++;
2314                 continue;
2315             }
2316         }
2317         if (*patternp != *bufp) return FALSE;
2318         patternp++;
2319         bufp++;
2320     }
2321 }
2322
2323 void
2324 SendToPlayer (char *data, int length)
2325 {
2326     int error, outCount;
2327     outCount = OutputToProcess(NoProc, data, length, &error);
2328     if (outCount < length) {
2329         DisplayFatalError(_("Error writing to display"), error, 1);
2330     }
2331 }
2332
2333 void
2334 PackHolding (char packed[], char *holding)
2335 {
2336     char *p = holding;
2337     char *q = packed;
2338     int runlength = 0;
2339     int curr = 9999;
2340     do {
2341         if (*p == curr) {
2342             runlength++;
2343         } else {
2344             switch (runlength) {
2345               case 0:
2346                 break;
2347               case 1:
2348                 *q++ = curr;
2349                 break;
2350               case 2:
2351                 *q++ = curr;
2352                 *q++ = curr;
2353                 break;
2354               default:
2355                 sprintf(q, "%d", runlength);
2356                 while (*q) q++;
2357                 *q++ = curr;
2358                 break;
2359             }
2360             runlength = 1;
2361             curr = *p;
2362         }
2363     } while (*p++);
2364     *q = NULLCHAR;
2365 }
2366
2367 /* Telnet protocol requests from the front end */
2368 void
2369 TelnetRequest (unsigned char ddww, unsigned char option)
2370 {
2371     unsigned char msg[3];
2372     int outCount, outError;
2373
2374     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2375
2376     if (appData.debugMode) {
2377         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2378         switch (ddww) {
2379           case TN_DO:
2380             ddwwStr = "DO";
2381             break;
2382           case TN_DONT:
2383             ddwwStr = "DONT";
2384             break;
2385           case TN_WILL:
2386             ddwwStr = "WILL";
2387             break;
2388           case TN_WONT:
2389             ddwwStr = "WONT";
2390             break;
2391           default:
2392             ddwwStr = buf1;
2393             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2394             break;
2395         }
2396         switch (option) {
2397           case TN_ECHO:
2398             optionStr = "ECHO";
2399             break;
2400           default:
2401             optionStr = buf2;
2402             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2403             break;
2404         }
2405         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2406     }
2407     msg[0] = TN_IAC;
2408     msg[1] = ddww;
2409     msg[2] = option;
2410     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2411     if (outCount < 3) {
2412         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2413     }
2414 }
2415
2416 void
2417 DoEcho ()
2418 {
2419     if (!appData.icsActive) return;
2420     TelnetRequest(TN_DO, TN_ECHO);
2421 }
2422
2423 void
2424 DontEcho ()
2425 {
2426     if (!appData.icsActive) return;
2427     TelnetRequest(TN_DONT, TN_ECHO);
2428 }
2429
2430 void
2431 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2432 {
2433     /* put the holdings sent to us by the server on the board holdings area */
2434     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2435     char p;
2436     ChessSquare piece;
2437
2438     if(gameInfo.holdingsWidth < 2)  return;
2439     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2440         return; // prevent overwriting by pre-board holdings
2441
2442     if( (int)lowestPiece >= BlackPawn ) {
2443         holdingsColumn = 0;
2444         countsColumn = 1;
2445         holdingsStartRow = BOARD_HEIGHT-1;
2446         direction = -1;
2447     } else {
2448         holdingsColumn = BOARD_WIDTH-1;
2449         countsColumn = BOARD_WIDTH-2;
2450         holdingsStartRow = 0;
2451         direction = 1;
2452     }
2453
2454     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2455         board[i][holdingsColumn] = EmptySquare;
2456         board[i][countsColumn]   = (ChessSquare) 0;
2457     }
2458     while( (p=*holdings++) != NULLCHAR ) {
2459         piece = CharToPiece( ToUpper(p) );
2460         if(piece == EmptySquare) continue;
2461         /*j = (int) piece - (int) WhitePawn;*/
2462         j = PieceToNumber(piece);
2463         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2464         if(j < 0) continue;               /* should not happen */
2465         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2466         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2467         board[holdingsStartRow+j*direction][countsColumn]++;
2468     }
2469 }
2470
2471
2472 void
2473 VariantSwitch (Board board, VariantClass newVariant)
2474 {
2475    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2476    static Board oldBoard;
2477
2478    startedFromPositionFile = FALSE;
2479    if(gameInfo.variant == newVariant) return;
2480
2481    /* [HGM] This routine is called each time an assignment is made to
2482     * gameInfo.variant during a game, to make sure the board sizes
2483     * are set to match the new variant. If that means adding or deleting
2484     * holdings, we shift the playing board accordingly
2485     * This kludge is needed because in ICS observe mode, we get boards
2486     * of an ongoing game without knowing the variant, and learn about the
2487     * latter only later. This can be because of the move list we requested,
2488     * in which case the game history is refilled from the beginning anyway,
2489     * but also when receiving holdings of a crazyhouse game. In the latter
2490     * case we want to add those holdings to the already received position.
2491     */
2492
2493
2494    if (appData.debugMode) {
2495      fprintf(debugFP, "Switch board from %s to %s\n",
2496              VariantName(gameInfo.variant), VariantName(newVariant));
2497      setbuf(debugFP, NULL);
2498    }
2499    shuffleOpenings = 0;       /* [HGM] shuffle */
2500    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2501    switch(newVariant)
2502      {
2503      case VariantShogi:
2504        newWidth = 9;  newHeight = 9;
2505        gameInfo.holdingsSize = 7;
2506      case VariantBughouse:
2507      case VariantCrazyhouse:
2508        newHoldingsWidth = 2; break;
2509      case VariantGreat:
2510        newWidth = 10;
2511      case VariantSuper:
2512        newHoldingsWidth = 2;
2513        gameInfo.holdingsSize = 8;
2514        break;
2515      case VariantGothic:
2516      case VariantCapablanca:
2517      case VariantCapaRandom:
2518        newWidth = 10;
2519      default:
2520        newHoldingsWidth = gameInfo.holdingsSize = 0;
2521      };
2522
2523    if(newWidth  != gameInfo.boardWidth  ||
2524       newHeight != gameInfo.boardHeight ||
2525       newHoldingsWidth != gameInfo.holdingsWidth ) {
2526
2527      /* shift position to new playing area, if needed */
2528      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2529        for(i=0; i<BOARD_HEIGHT; i++)
2530          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2531            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2532              board[i][j];
2533        for(i=0; i<newHeight; i++) {
2534          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2535          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2536        }
2537      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2538        for(i=0; i<BOARD_HEIGHT; i++)
2539          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2540            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2541              board[i][j];
2542      }
2543      board[HOLDINGS_SET] = 0;
2544      gameInfo.boardWidth  = newWidth;
2545      gameInfo.boardHeight = newHeight;
2546      gameInfo.holdingsWidth = newHoldingsWidth;
2547      gameInfo.variant = newVariant;
2548      InitDrawingSizes(-2, 0);
2549    } else gameInfo.variant = newVariant;
2550    CopyBoard(oldBoard, board);   // remember correctly formatted board
2551      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2552    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2553 }
2554
2555 static int loggedOn = FALSE;
2556
2557 /*-- Game start info cache: --*/
2558 int gs_gamenum;
2559 char gs_kind[MSG_SIZ];
2560 static char player1Name[128] = "";
2561 static char player2Name[128] = "";
2562 static char cont_seq[] = "\n\\   ";
2563 static int player1Rating = -1;
2564 static int player2Rating = -1;
2565 /*----------------------------*/
2566
2567 ColorClass curColor = ColorNormal;
2568 int suppressKibitz = 0;
2569
2570 // [HGM] seekgraph
2571 Boolean soughtPending = FALSE;
2572 Boolean seekGraphUp;
2573 #define MAX_SEEK_ADS 200
2574 #define SQUARE 0x80
2575 char *seekAdList[MAX_SEEK_ADS];
2576 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2577 float tcList[MAX_SEEK_ADS];
2578 char colorList[MAX_SEEK_ADS];
2579 int nrOfSeekAds = 0;
2580 int minRating = 1010, maxRating = 2800;
2581 int hMargin = 10, vMargin = 20, h, w;
2582 extern int squareSize, lineGap;
2583
2584 void
2585 PlotSeekAd (int i)
2586 {
2587         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2588         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2589         if(r < minRating+100 && r >=0 ) r = minRating+100;
2590         if(r > maxRating) r = maxRating;
2591         if(tc < 1.f) tc = 1.f;
2592         if(tc > 95.f) tc = 95.f;
2593         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2594         y = ((double)r - minRating)/(maxRating - minRating)
2595             * (h-vMargin-squareSize/8-1) + vMargin;
2596         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2597         if(strstr(seekAdList[i], " u ")) color = 1;
2598         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2599            !strstr(seekAdList[i], "bullet") &&
2600            !strstr(seekAdList[i], "blitz") &&
2601            !strstr(seekAdList[i], "standard") ) color = 2;
2602         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2603         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2604 }
2605
2606 void
2607 PlotSingleSeekAd (int i)
2608 {
2609         PlotSeekAd(i);
2610 }
2611
2612 void
2613 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2614 {
2615         char buf[MSG_SIZ], *ext = "";
2616         VariantClass v = StringToVariant(type);
2617         if(strstr(type, "wild")) {
2618             ext = type + 4; // append wild number
2619             if(v == VariantFischeRandom) type = "chess960"; else
2620             if(v == VariantLoadable) type = "setup"; else
2621             type = VariantName(v);
2622         }
2623         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2624         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2625             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2626             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2627             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2628             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2629             seekNrList[nrOfSeekAds] = nr;
2630             zList[nrOfSeekAds] = 0;
2631             seekAdList[nrOfSeekAds++] = StrSave(buf);
2632             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2633         }
2634 }
2635
2636 void
2637 EraseSeekDot (int i)
2638 {
2639     int x = xList[i], y = yList[i], d=squareSize/4, k;
2640     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2641     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2642     // now replot every dot that overlapped
2643     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2644         int xx = xList[k], yy = yList[k];
2645         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2646             DrawSeekDot(xx, yy, colorList[k]);
2647     }
2648 }
2649
2650 void
2651 RemoveSeekAd (int nr)
2652 {
2653         int i;
2654         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2655             EraseSeekDot(i);
2656             if(seekAdList[i]) free(seekAdList[i]);
2657             seekAdList[i] = seekAdList[--nrOfSeekAds];
2658             seekNrList[i] = seekNrList[nrOfSeekAds];
2659             ratingList[i] = ratingList[nrOfSeekAds];
2660             colorList[i]  = colorList[nrOfSeekAds];
2661             tcList[i] = tcList[nrOfSeekAds];
2662             xList[i]  = xList[nrOfSeekAds];
2663             yList[i]  = yList[nrOfSeekAds];
2664             zList[i]  = zList[nrOfSeekAds];
2665             seekAdList[nrOfSeekAds] = NULL;
2666             break;
2667         }
2668 }
2669
2670 Boolean
2671 MatchSoughtLine (char *line)
2672 {
2673     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2674     int nr, base, inc, u=0; char dummy;
2675
2676     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2677        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2678        (u=1) &&
2679        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2680         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2681         // match: compact and save the line
2682         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2683         return TRUE;
2684     }
2685     return FALSE;
2686 }
2687
2688 int
2689 DrawSeekGraph ()
2690 {
2691     int i;
2692     if(!seekGraphUp) return FALSE;
2693     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2694     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2695
2696     DrawSeekBackground(0, 0, w, h);
2697     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2698     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2699     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2700         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2701         yy = h-1-yy;
2702         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2703         if(i%500 == 0) {
2704             char buf[MSG_SIZ];
2705             snprintf(buf, MSG_SIZ, "%d", i);
2706             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2707         }
2708     }
2709     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2710     for(i=1; i<100; i+=(i<10?1:5)) {
2711         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2712         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2713         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2714             char buf[MSG_SIZ];
2715             snprintf(buf, MSG_SIZ, "%d", i);
2716             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2717         }
2718     }
2719     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2720     return TRUE;
2721 }
2722
2723 int
2724 SeekGraphClick (ClickType click, int x, int y, int moving)
2725 {
2726     static int lastDown = 0, displayed = 0, lastSecond;
2727     if(y < 0) return FALSE;
2728     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2729         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2730         if(!seekGraphUp) return FALSE;
2731         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2732         DrawPosition(TRUE, NULL);
2733         return TRUE;
2734     }
2735     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2736         if(click == Release || moving) return FALSE;
2737         nrOfSeekAds = 0;
2738         soughtPending = TRUE;
2739         SendToICS(ics_prefix);
2740         SendToICS("sought\n"); // should this be "sought all"?
2741     } else { // issue challenge based on clicked ad
2742         int dist = 10000; int i, closest = 0, second = 0;
2743         for(i=0; i<nrOfSeekAds; i++) {
2744             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2745             if(d < dist) { dist = d; closest = i; }
2746             second += (d - zList[i] < 120); // count in-range ads
2747             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2748         }
2749         if(dist < 120) {
2750             char buf[MSG_SIZ];
2751             second = (second > 1);
2752             if(displayed != closest || second != lastSecond) {
2753                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2754                 lastSecond = second; displayed = closest;
2755             }
2756             if(click == Press) {
2757                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2758                 lastDown = closest;
2759                 return TRUE;
2760             } // on press 'hit', only show info
2761             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2762             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2763             SendToICS(ics_prefix);
2764             SendToICS(buf);
2765             return TRUE; // let incoming board of started game pop down the graph
2766         } else if(click == Release) { // release 'miss' is ignored
2767             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2768             if(moving == 2) { // right up-click
2769                 nrOfSeekAds = 0; // refresh graph
2770                 soughtPending = TRUE;
2771                 SendToICS(ics_prefix);
2772                 SendToICS("sought\n"); // should this be "sought all"?
2773             }
2774             return TRUE;
2775         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2776         // press miss or release hit 'pop down' seek graph
2777         seekGraphUp = FALSE;
2778         DrawPosition(TRUE, NULL);
2779     }
2780     return TRUE;
2781 }
2782
2783 void
2784 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2785 {
2786 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2787 #define STARTED_NONE 0
2788 #define STARTED_MOVES 1
2789 #define STARTED_BOARD 2
2790 #define STARTED_OBSERVE 3
2791 #define STARTED_HOLDINGS 4
2792 #define STARTED_CHATTER 5
2793 #define STARTED_COMMENT 6
2794 #define STARTED_MOVES_NOHIDE 7
2795
2796     static int started = STARTED_NONE;
2797     static char parse[20000];
2798     static int parse_pos = 0;
2799     static char buf[BUF_SIZE + 1];
2800     static int firstTime = TRUE, intfSet = FALSE;
2801     static ColorClass prevColor = ColorNormal;
2802     static int savingComment = FALSE;
2803     static int cmatch = 0; // continuation sequence match
2804     char *bp;
2805     char str[MSG_SIZ];
2806     int i, oldi;
2807     int buf_len;
2808     int next_out;
2809     int tkind;
2810     int backup;    /* [DM] For zippy color lines */
2811     char *p;
2812     char talker[MSG_SIZ]; // [HGM] chat
2813     int channel;
2814
2815     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2816
2817     if (appData.debugMode) {
2818       if (!error) {
2819         fprintf(debugFP, "<ICS: ");
2820         show_bytes(debugFP, data, count);
2821         fprintf(debugFP, "\n");
2822       }
2823     }
2824
2825     if (appData.debugMode) { int f = forwardMostMove;
2826         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2827                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2828                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2829     }
2830     if (count > 0) {
2831         /* If last read ended with a partial line that we couldn't parse,
2832            prepend it to the new read and try again. */
2833         if (leftover_len > 0) {
2834             for (i=0; i<leftover_len; i++)
2835               buf[i] = buf[leftover_start + i];
2836         }
2837
2838     /* copy new characters into the buffer */
2839     bp = buf + leftover_len;
2840     buf_len=leftover_len;
2841     for (i=0; i<count; i++)
2842     {
2843         // ignore these
2844         if (data[i] == '\r')
2845             continue;
2846
2847         // join lines split by ICS?
2848         if (!appData.noJoin)
2849         {
2850             /*
2851                 Joining just consists of finding matches against the
2852                 continuation sequence, and discarding that sequence
2853                 if found instead of copying it.  So, until a match
2854                 fails, there's nothing to do since it might be the
2855                 complete sequence, and thus, something we don't want
2856                 copied.
2857             */
2858             if (data[i] == cont_seq[cmatch])
2859             {
2860                 cmatch++;
2861                 if (cmatch == strlen(cont_seq))
2862                 {
2863                     cmatch = 0; // complete match.  just reset the counter
2864
2865                     /*
2866                         it's possible for the ICS to not include the space
2867                         at the end of the last word, making our [correct]
2868                         join operation fuse two separate words.  the server
2869                         does this when the space occurs at the width setting.
2870                     */
2871                     if (!buf_len || buf[buf_len-1] != ' ')
2872                     {
2873                         *bp++ = ' ';
2874                         buf_len++;
2875                     }
2876                 }
2877                 continue;
2878             }
2879             else if (cmatch)
2880             {
2881                 /*
2882                     match failed, so we have to copy what matched before
2883                     falling through and copying this character.  In reality,
2884                     this will only ever be just the newline character, but
2885                     it doesn't hurt to be precise.
2886                 */
2887                 strncpy(bp, cont_seq, cmatch);
2888                 bp += cmatch;
2889                 buf_len += cmatch;
2890                 cmatch = 0;
2891             }
2892         }
2893
2894         // copy this char
2895         *bp++ = data[i];
2896         buf_len++;
2897     }
2898
2899         buf[buf_len] = NULLCHAR;
2900 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2901         next_out = 0;
2902         leftover_start = 0;
2903
2904         i = 0;
2905         while (i < buf_len) {
2906             /* Deal with part of the TELNET option negotiation
2907                protocol.  We refuse to do anything beyond the
2908                defaults, except that we allow the WILL ECHO option,
2909                which ICS uses to turn off password echoing when we are
2910                directly connected to it.  We reject this option
2911                if localLineEditing mode is on (always on in xboard)
2912                and we are talking to port 23, which might be a real
2913                telnet server that will try to keep WILL ECHO on permanently.
2914              */
2915             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2916                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2917                 unsigned char option;
2918                 oldi = i;
2919                 switch ((unsigned char) buf[++i]) {
2920                   case TN_WILL:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WILL ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (remoteEchoOption) break;
2930                         if (appData.localLineEditing &&
2931                             atoi(appData.icsPort) == TN_PORT) {
2932                             TelnetRequest(TN_DONT, TN_ECHO);
2933                         } else {
2934                             EchoOff();
2935                             TelnetRequest(TN_DO, TN_ECHO);
2936                             remoteEchoOption = TRUE;
2937                         }
2938                         break;
2939                       default:
2940                         if (appData.debugMode)
2941                           fprintf(debugFP, "%d ", option);
2942                         /* Whatever this is, we don't want it. */
2943                         TelnetRequest(TN_DONT, option);
2944                         break;
2945                     }
2946                     break;
2947                   case TN_WONT:
2948                     if (appData.debugMode)
2949                       fprintf(debugFP, "\n<WONT ");
2950                     switch (option = (unsigned char) buf[++i]) {
2951                       case TN_ECHO:
2952                         if (appData.debugMode)
2953                           fprintf(debugFP, "ECHO ");
2954                         /* Reply only if this is a change, according
2955                            to the protocol rules. */
2956                         if (!remoteEchoOption) break;
2957                         EchoOn();
2958                         TelnetRequest(TN_DONT, TN_ECHO);
2959                         remoteEchoOption = FALSE;
2960                         break;
2961                       default:
2962                         if (appData.debugMode)
2963                           fprintf(debugFP, "%d ", (unsigned char) option);
2964                         /* Whatever this is, it must already be turned
2965                            off, because we never agree to turn on
2966                            anything non-default, so according to the
2967                            protocol rules, we don't reply. */
2968                         break;
2969                     }
2970                     break;
2971                   case TN_DO:
2972                     if (appData.debugMode)
2973                       fprintf(debugFP, "\n<DO ");
2974                     switch (option = (unsigned char) buf[++i]) {
2975                       default:
2976                         /* Whatever this is, we refuse to do it. */
2977                         if (appData.debugMode)
2978                           fprintf(debugFP, "%d ", option);
2979                         TelnetRequest(TN_WONT, option);
2980                         break;
2981                     }
2982                     break;
2983                   case TN_DONT:
2984                     if (appData.debugMode)
2985                       fprintf(debugFP, "\n<DONT ");
2986                     switch (option = (unsigned char) buf[++i]) {
2987                       default:
2988                         if (appData.debugMode)
2989                           fprintf(debugFP, "%d ", option);
2990                         /* Whatever this is, we are already not doing
2991                            it, because we never agree to do anything
2992                            non-default, so according to the protocol
2993                            rules, we don't reply. */
2994                         break;
2995                     }
2996                     break;
2997                   case TN_IAC:
2998                     if (appData.debugMode)
2999                       fprintf(debugFP, "\n<IAC ");
3000                     /* Doubled IAC; pass it through */
3001                     i--;
3002                     break;
3003                   default:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3006                     /* Drop all other telnet commands on the floor */
3007                     break;
3008                 }
3009                 if (oldi > next_out)
3010                   SendToPlayer(&buf[next_out], oldi - next_out);
3011                 if (++i > next_out)
3012                   next_out = i;
3013                 continue;
3014             }
3015
3016             /* OK, this at least will *usually* work */
3017             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3018                 loggedOn = TRUE;
3019             }
3020
3021             if (loggedOn && !intfSet) {
3022                 if (ics_type == ICS_ICC) {
3023                   snprintf(str, MSG_SIZ,
3024                           "/set-quietly interface %s\n/set-quietly style 12\n",
3025                           programVersion);
3026                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3027                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3028                 } else if (ics_type == ICS_CHESSNET) {
3029                   snprintf(str, MSG_SIZ, "/style 12\n");
3030                 } else {
3031                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3032                   strcat(str, programVersion);
3033                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3034                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3035                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3036 #ifdef WIN32
3037                   strcat(str, "$iset nohighlight 1\n");
3038 #endif
3039                   strcat(str, "$iset lock 1\n$style 12\n");
3040                 }
3041                 SendToICS(str);
3042                 NotifyFrontendLogin();
3043                 intfSet = TRUE;
3044             }
3045
3046             if (started == STARTED_COMMENT) {
3047                 /* Accumulate characters in comment */
3048                 parse[parse_pos++] = buf[i];
3049                 if (buf[i] == '\n') {
3050                     parse[parse_pos] = NULLCHAR;
3051                     if(chattingPartner>=0) {
3052                         char mess[MSG_SIZ];
3053                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3054                         OutputChatMessage(chattingPartner, mess);
3055                         chattingPartner = -1;
3056                         next_out = i+1; // [HGM] suppress printing in ICS window
3057                     } else
3058                     if(!suppressKibitz) // [HGM] kibitz
3059                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3060                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3061                         int nrDigit = 0, nrAlph = 0, j;
3062                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3063                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3064                         parse[parse_pos] = NULLCHAR;
3065                         // try to be smart: if it does not look like search info, it should go to
3066                         // ICS interaction window after all, not to engine-output window.
3067                         for(j=0; j<parse_pos; j++) { // count letters and digits
3068                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3069                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3070                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3071                         }
3072                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3073                             int depth=0; float score;
3074                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3075                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3076                                 pvInfoList[forwardMostMove-1].depth = depth;
3077                                 pvInfoList[forwardMostMove-1].score = 100*score;
3078                             }
3079                             OutputKibitz(suppressKibitz, parse);
3080                         } else {
3081                             char tmp[MSG_SIZ];
3082                             if(gameMode == IcsObserving) // restore original ICS messages
3083                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3084                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3085                             else
3086                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3087                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3088                             SendToPlayer(tmp, strlen(tmp));
3089                         }
3090                         next_out = i+1; // [HGM] suppress printing in ICS window
3091                     }
3092                     started = STARTED_NONE;
3093                 } else {
3094                     /* Don't match patterns against characters in comment */
3095                     i++;
3096                     continue;
3097                 }
3098             }
3099             if (started == STARTED_CHATTER) {
3100                 if (buf[i] != '\n') {
3101                     /* Don't match patterns against characters in chatter */
3102                     i++;
3103                     continue;
3104                 }
3105                 started = STARTED_NONE;
3106                 if(suppressKibitz) next_out = i+1;
3107             }
3108
3109             /* Kludge to deal with rcmd protocol */
3110             if (firstTime && looking_at(buf, &i, "\001*")) {
3111                 DisplayFatalError(&buf[1], 0, 1);
3112                 continue;
3113             } else {
3114                 firstTime = FALSE;
3115             }
3116
3117             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3118                 ics_type = ICS_ICC;
3119                 ics_prefix = "/";
3120                 if (appData.debugMode)
3121                   fprintf(debugFP, "ics_type %d\n", ics_type);
3122                 continue;
3123             }
3124             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3125                 ics_type = ICS_FICS;
3126                 ics_prefix = "$";
3127                 if (appData.debugMode)
3128                   fprintf(debugFP, "ics_type %d\n", ics_type);
3129                 continue;
3130             }
3131             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3132                 ics_type = ICS_CHESSNET;
3133                 ics_prefix = "/";
3134                 if (appData.debugMode)
3135                   fprintf(debugFP, "ics_type %d\n", ics_type);
3136                 continue;
3137             }
3138
3139             if (!loggedOn &&
3140                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3141                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3142                  looking_at(buf, &i, "will be \"*\""))) {
3143               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3144               continue;
3145             }
3146
3147             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3148               char buf[MSG_SIZ];
3149               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3150               DisplayIcsInteractionTitle(buf);
3151               have_set_title = TRUE;
3152             }
3153
3154             /* skip finger notes */
3155             if (started == STARTED_NONE &&
3156                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3157                  (buf[i] == '1' && buf[i+1] == '0')) &&
3158                 buf[i+2] == ':' && buf[i+3] == ' ') {
3159               started = STARTED_CHATTER;
3160               i += 3;
3161               continue;
3162             }
3163
3164             oldi = i;
3165             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3166             if(appData.seekGraph) {
3167                 if(soughtPending && MatchSoughtLine(buf+i)) {
3168                     i = strstr(buf+i, "rated") - buf;
3169                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3170                     next_out = leftover_start = i;
3171                     started = STARTED_CHATTER;
3172                     suppressKibitz = TRUE;
3173                     continue;
3174                 }
3175                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3176                         && looking_at(buf, &i, "* ads displayed")) {
3177                     soughtPending = FALSE;
3178                     seekGraphUp = TRUE;
3179                     DrawSeekGraph();
3180                     continue;
3181                 }
3182                 if(appData.autoRefresh) {
3183                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3184                         int s = (ics_type == ICS_ICC); // ICC format differs
3185                         if(seekGraphUp)
3186                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3187                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3188                         looking_at(buf, &i, "*% "); // eat prompt
3189                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3190                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                         next_out = i; // suppress
3192                         continue;
3193                     }
3194                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3195                         char *p = star_match[0];
3196                         while(*p) {
3197                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3198                             while(*p && *p++ != ' '); // next
3199                         }
3200                         looking_at(buf, &i, "*% "); // eat prompt
3201                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3202                         next_out = i;
3203                         continue;
3204                     }
3205                 }
3206             }
3207
3208             /* skip formula vars */
3209             if (started == STARTED_NONE &&
3210                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3211               started = STARTED_CHATTER;
3212               i += 3;
3213               continue;
3214             }
3215
3216             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3217             if (appData.autoKibitz && started == STARTED_NONE &&
3218                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3219                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3220                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3221                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3222                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3223                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3224                         suppressKibitz = TRUE;
3225                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3226                         next_out = i;
3227                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3228                                 && (gameMode == IcsPlayingWhite)) ||
3229                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3230                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3231                             started = STARTED_CHATTER; // own kibitz we simply discard
3232                         else {
3233                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3234                             parse_pos = 0; parse[0] = NULLCHAR;
3235                             savingComment = TRUE;
3236                             suppressKibitz = gameMode != IcsObserving ? 2 :
3237                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3238                         }
3239                         continue;
3240                 } else
3241                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3242                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3243                          && atoi(star_match[0])) {
3244                     // suppress the acknowledgements of our own autoKibitz
3245                     char *p;
3246                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3248                     SendToPlayer(star_match[0], strlen(star_match[0]));
3249                     if(looking_at(buf, &i, "*% ")) // eat prompt
3250                         suppressKibitz = FALSE;
3251                     next_out = i;
3252                     continue;
3253                 }
3254             } // [HGM] kibitz: end of patch
3255
3256             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3257
3258             // [HGM] chat: intercept tells by users for which we have an open chat window
3259             channel = -1;
3260             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3261                                            looking_at(buf, &i, "* whispers:") ||
3262                                            looking_at(buf, &i, "* kibitzes:") ||
3263                                            looking_at(buf, &i, "* shouts:") ||
3264                                            looking_at(buf, &i, "* c-shouts:") ||
3265                                            looking_at(buf, &i, "--> * ") ||
3266                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3267                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3268                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3269                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3270                 int p;
3271                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3272                 chattingPartner = -1;
3273
3274                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3275                 for(p=0; p<MAX_CHAT; p++) {
3276                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3277                     talker[0] = '['; strcat(talker, "] ");
3278                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3279                     chattingPartner = p; break;
3280                     }
3281                 } else
3282                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3283                 for(p=0; p<MAX_CHAT; p++) {
3284                     if(!strcmp("kibitzes", chatPartner[p])) {
3285                         talker[0] = '['; strcat(talker, "] ");
3286                         chattingPartner = p; break;
3287                     }
3288                 } else
3289                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3290                 for(p=0; p<MAX_CHAT; p++) {
3291                     if(!strcmp("whispers", chatPartner[p])) {
3292                         talker[0] = '['; strcat(talker, "] ");
3293                         chattingPartner = p; break;
3294                     }
3295                 } else
3296                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3297                   if(buf[i-8] == '-' && buf[i-3] == 't')
3298                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3299                     if(!strcmp("c-shouts", chatPartner[p])) {
3300                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3301                         chattingPartner = p; break;
3302                     }
3303                   }
3304                   if(chattingPartner < 0)
3305                   for(p=0; p<MAX_CHAT; p++) {
3306                     if(!strcmp("shouts", chatPartner[p])) {
3307                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3308                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3309                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3310                         chattingPartner = p; break;
3311                     }
3312                   }
3313                 }
3314                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3315                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3316                     talker[0] = 0; Colorize(ColorTell, FALSE);
3317                     chattingPartner = p; break;
3318                 }
3319                 if(chattingPartner<0) i = oldi; else {
3320                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3321                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3322                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3323                     started = STARTED_COMMENT;
3324                     parse_pos = 0; parse[0] = NULLCHAR;
3325                     savingComment = 3 + chattingPartner; // counts as TRUE
3326                     suppressKibitz = TRUE;
3327                     continue;
3328                 }
3329             } // [HGM] chat: end of patch
3330
3331           backup = i;
3332             if (appData.zippyTalk || appData.zippyPlay) {
3333                 /* [DM] Backup address for color zippy lines */
3334 #if ZIPPY
3335                if (loggedOn == TRUE)
3336                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3337                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3338 #endif
3339             } // [DM] 'else { ' deleted
3340                 if (
3341                     /* Regular tells and says */
3342                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3343                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3344                     looking_at(buf, &i, "* says: ") ||
3345                     /* Don't color "message" or "messages" output */
3346                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3347                     looking_at(buf, &i, "*. * at *:*: ") ||
3348                     looking_at(buf, &i, "--* (*:*): ") ||
3349                     /* Message notifications (same color as tells) */
3350                     looking_at(buf, &i, "* has left a message ") ||
3351                     looking_at(buf, &i, "* just sent you a message:\n") ||
3352                     /* Whispers and kibitzes */
3353                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3354                     looking_at(buf, &i, "* kibitzes: ") ||
3355                     /* Channel tells */
3356                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3357
3358                   if (tkind == 1 && strchr(star_match[0], ':')) {
3359                       /* Avoid "tells you:" spoofs in channels */
3360                      tkind = 3;
3361                   }
3362                   if (star_match[0][0] == NULLCHAR ||
3363                       strchr(star_match[0], ' ') ||
3364                       (tkind == 3 && strchr(star_match[1], ' '))) {
3365                     /* Reject bogus matches */
3366                     i = oldi;
3367                   } else {
3368                     if (appData.colorize) {
3369                       if (oldi > next_out) {
3370                         SendToPlayer(&buf[next_out], oldi - next_out);
3371                         next_out = oldi;
3372                       }
3373                       switch (tkind) {
3374                       case 1:
3375                         Colorize(ColorTell, FALSE);
3376                         curColor = ColorTell;
3377                         break;
3378                       case 2:
3379                         Colorize(ColorKibitz, FALSE);
3380                         curColor = ColorKibitz;
3381                         break;
3382                       case 3:
3383                         p = strrchr(star_match[1], '(');
3384                         if (p == NULL) {
3385                           p = star_match[1];
3386                         } else {
3387                           p++;
3388                         }
3389                         if (atoi(p) == 1) {
3390                           Colorize(ColorChannel1, FALSE);
3391                           curColor = ColorChannel1;
3392                         } else {
3393                           Colorize(ColorChannel, FALSE);
3394                           curColor = ColorChannel;
3395                         }
3396                         break;
3397                       case 5:
3398                         curColor = ColorNormal;
3399                         break;
3400                       }
3401                     }
3402                     if (started == STARTED_NONE && appData.autoComment &&
3403                         (gameMode == IcsObserving ||
3404                          gameMode == IcsPlayingWhite ||
3405                          gameMode == IcsPlayingBlack)) {
3406                       parse_pos = i - oldi;
3407                       memcpy(parse, &buf[oldi], parse_pos);
3408                       parse[parse_pos] = NULLCHAR;
3409                       started = STARTED_COMMENT;
3410                       savingComment = TRUE;
3411                     } else {
3412                       started = STARTED_CHATTER;
3413                       savingComment = FALSE;
3414                     }
3415                     loggedOn = TRUE;
3416                     continue;
3417                   }
3418                 }
3419
3420                 if (looking_at(buf, &i, "* s-shouts: ") ||
3421                     looking_at(buf, &i, "* c-shouts: ")) {
3422                     if (appData.colorize) {
3423                         if (oldi > next_out) {
3424                             SendToPlayer(&buf[next_out], oldi - next_out);
3425                             next_out = oldi;
3426                         }
3427                         Colorize(ColorSShout, FALSE);
3428                         curColor = ColorSShout;
3429                     }
3430                     loggedOn = TRUE;
3431                     started = STARTED_CHATTER;
3432                     continue;
3433                 }
3434
3435                 if (looking_at(buf, &i, "--->")) {
3436                     loggedOn = TRUE;
3437                     continue;
3438                 }
3439
3440                 if (looking_at(buf, &i, "* shouts: ") ||
3441                     looking_at(buf, &i, "--> ")) {
3442                     if (appData.colorize) {
3443                         if (oldi > next_out) {
3444                             SendToPlayer(&buf[next_out], oldi - next_out);
3445                             next_out = oldi;
3446                         }
3447                         Colorize(ColorShout, FALSE);
3448                         curColor = ColorShout;
3449                     }
3450                     loggedOn = TRUE;
3451                     started = STARTED_CHATTER;
3452                     continue;
3453                 }
3454
3455                 if (looking_at( buf, &i, "Challenge:")) {
3456                     if (appData.colorize) {
3457                         if (oldi > next_out) {
3458                             SendToPlayer(&buf[next_out], oldi - next_out);
3459                             next_out = oldi;
3460                         }
3461                         Colorize(ColorChallenge, FALSE);
3462                         curColor = ColorChallenge;
3463                     }
3464                     loggedOn = TRUE;
3465                     continue;
3466                 }
3467
3468                 if (looking_at(buf, &i, "* offers you") ||
3469                     looking_at(buf, &i, "* offers to be") ||
3470                     looking_at(buf, &i, "* would like to") ||
3471                     looking_at(buf, &i, "* requests to") ||
3472                     looking_at(buf, &i, "Your opponent offers") ||
3473                     looking_at(buf, &i, "Your opponent requests")) {
3474
3475                     if (appData.colorize) {
3476                         if (oldi > next_out) {
3477                             SendToPlayer(&buf[next_out], oldi - next_out);
3478                             next_out = oldi;
3479                         }
3480                         Colorize(ColorRequest, FALSE);
3481                         curColor = ColorRequest;
3482                     }
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "* (*) seeking")) {
3487                     if (appData.colorize) {
3488                         if (oldi > next_out) {
3489                             SendToPlayer(&buf[next_out], oldi - next_out);
3490                             next_out = oldi;
3491                         }
3492                         Colorize(ColorSeek, FALSE);
3493                         curColor = ColorSeek;
3494                     }
3495                     continue;
3496             }
3497
3498           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3499
3500             if (looking_at(buf, &i, "\\   ")) {
3501                 if (prevColor != ColorNormal) {
3502                     if (oldi > next_out) {
3503                         SendToPlayer(&buf[next_out], oldi - next_out);
3504                         next_out = oldi;
3505                     }
3506                     Colorize(prevColor, TRUE);
3507                     curColor = prevColor;
3508                 }
3509                 if (savingComment) {
3510                     parse_pos = i - oldi;
3511                     memcpy(parse, &buf[oldi], parse_pos);
3512                     parse[parse_pos] = NULLCHAR;
3513                     started = STARTED_COMMENT;
3514                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3515                         chattingPartner = savingComment - 3; // kludge to remember the box
3516                 } else {
3517                     started = STARTED_CHATTER;
3518                 }
3519                 continue;
3520             }
3521
3522             if (looking_at(buf, &i, "Black Strength :") ||
3523                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3524                 looking_at(buf, &i, "<10>") ||
3525                 looking_at(buf, &i, "#@#")) {
3526                 /* Wrong board style */
3527                 loggedOn = TRUE;
3528                 SendToICS(ics_prefix);
3529                 SendToICS("set style 12\n");
3530                 SendToICS(ics_prefix);
3531                 SendToICS("refresh\n");
3532                 continue;
3533             }
3534
3535             if (looking_at(buf, &i, "login:")) {
3536               if (!have_sent_ICS_logon) {
3537                 if(ICSInitScript())
3538                   have_sent_ICS_logon = 1;
3539                 else // no init script was found
3540                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3541               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3542                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3543               }
3544                 continue;
3545             }
3546
3547             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3548                 (looking_at(buf, &i, "\n<12> ") ||
3549                  looking_at(buf, &i, "<12> "))) {
3550                 loggedOn = TRUE;
3551                 if (oldi > next_out) {
3552                     SendToPlayer(&buf[next_out], oldi - next_out);
3553                 }
3554                 next_out = i;
3555                 started = STARTED_BOARD;
3556                 parse_pos = 0;
3557                 continue;
3558             }
3559
3560             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3561                 looking_at(buf, &i, "<b1> ")) {
3562                 if (oldi > next_out) {
3563                     SendToPlayer(&buf[next_out], oldi - next_out);
3564                 }
3565                 next_out = i;
3566                 started = STARTED_HOLDINGS;
3567                 parse_pos = 0;
3568                 continue;
3569             }
3570
3571             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3572                 loggedOn = TRUE;
3573                 /* Header for a move list -- first line */
3574
3575                 switch (ics_getting_history) {
3576                   case H_FALSE:
3577                     switch (gameMode) {
3578                       case IcsIdle:
3579                       case BeginningOfGame:
3580                         /* User typed "moves" or "oldmoves" while we
3581                            were idle.  Pretend we asked for these
3582                            moves and soak them up so user can step
3583                            through them and/or save them.
3584                            */
3585                         Reset(FALSE, TRUE);
3586                         gameMode = IcsObserving;
3587                         ModeHighlight();
3588                         ics_gamenum = -1;
3589                         ics_getting_history = H_GOT_UNREQ_HEADER;
3590                         break;
3591                       case EditGame: /*?*/
3592                       case EditPosition: /*?*/
3593                         /* Should above feature work in these modes too? */
3594                         /* For now it doesn't */
3595                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3596                         break;
3597                       default:
3598                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3599                         break;
3600                     }
3601                     break;
3602                   case H_REQUESTED:
3603                     /* Is this the right one? */
3604                     if (gameInfo.white && gameInfo.black &&
3605                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3606                         strcmp(gameInfo.black, star_match[2]) == 0) {
3607                         /* All is well */
3608                         ics_getting_history = H_GOT_REQ_HEADER;
3609                     }
3610                     break;
3611                   case H_GOT_REQ_HEADER:
3612                   case H_GOT_UNREQ_HEADER:
3613                   case H_GOT_UNWANTED_HEADER:
3614                   case H_GETTING_MOVES:
3615                     /* Should not happen */
3616                     DisplayError(_("Error gathering move list: two headers"), 0);
3617                     ics_getting_history = H_FALSE;
3618                     break;
3619                 }
3620
3621                 /* Save player ratings into gameInfo if needed */
3622                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3623                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3624                     (gameInfo.whiteRating == -1 ||
3625                      gameInfo.blackRating == -1)) {
3626
3627                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3628                     gameInfo.blackRating = string_to_rating(star_match[3]);
3629                     if (appData.debugMode)
3630                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3631                               gameInfo.whiteRating, gameInfo.blackRating);
3632                 }
3633                 continue;
3634             }
3635
3636             if (looking_at(buf, &i,
3637               "* * match, initial time: * minute*, increment: * second")) {
3638                 /* Header for a move list -- second line */
3639                 /* Initial board will follow if this is a wild game */
3640                 if (gameInfo.event != NULL) free(gameInfo.event);
3641                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3642                 gameInfo.event = StrSave(str);
3643                 /* [HGM] we switched variant. Translate boards if needed. */
3644                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3645                 continue;
3646             }
3647
3648             if (looking_at(buf, &i, "Move  ")) {
3649                 /* Beginning of a move list */
3650                 switch (ics_getting_history) {
3651                   case H_FALSE:
3652                     /* Normally should not happen */
3653                     /* Maybe user hit reset while we were parsing */
3654                     break;
3655                   case H_REQUESTED:
3656                     /* Happens if we are ignoring a move list that is not
3657                      * the one we just requested.  Common if the user
3658                      * tries to observe two games without turning off
3659                      * getMoveList */
3660                     break;
3661                   case H_GETTING_MOVES:
3662                     /* Should not happen */
3663                     DisplayError(_("Error gathering move list: nested"), 0);
3664                     ics_getting_history = H_FALSE;
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                     ics_getting_history = H_GETTING_MOVES;
3668                     started = STARTED_MOVES;
3669                     parse_pos = 0;
3670                     if (oldi > next_out) {
3671                         SendToPlayer(&buf[next_out], oldi - next_out);
3672                     }
3673                     break;
3674                   case H_GOT_UNREQ_HEADER:
3675                     ics_getting_history = H_GETTING_MOVES;
3676                     started = STARTED_MOVES_NOHIDE;
3677                     parse_pos = 0;
3678                     break;
3679                   case H_GOT_UNWANTED_HEADER:
3680                     ics_getting_history = H_FALSE;
3681                     break;
3682                 }
3683                 continue;
3684             }
3685
3686             if (looking_at(buf, &i, "% ") ||
3687                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3688                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3689                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3690                     soughtPending = FALSE;
3691                     seekGraphUp = TRUE;
3692                     DrawSeekGraph();
3693                 }
3694                 if(suppressKibitz) next_out = i;
3695                 savingComment = FALSE;
3696                 suppressKibitz = 0;
3697                 switch (started) {
3698                   case STARTED_MOVES:
3699                   case STARTED_MOVES_NOHIDE:
3700                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3701                     parse[parse_pos + i - oldi] = NULLCHAR;
3702                     ParseGameHistory(parse);
3703 #if ZIPPY
3704                     if (appData.zippyPlay && first.initDone) {
3705                         FeedMovesToProgram(&first, forwardMostMove);
3706                         if (gameMode == IcsPlayingWhite) {
3707                             if (WhiteOnMove(forwardMostMove)) {
3708                                 if (first.sendTime) {
3709                                   if (first.useColors) {
3710                                     SendToProgram("black\n", &first);
3711                                   }
3712                                   SendTimeRemaining(&first, TRUE);
3713                                 }
3714                                 if (first.useColors) {
3715                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3716                                 }
3717                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3718                                 first.maybeThinking = TRUE;
3719                             } else {
3720                                 if (first.usePlayother) {
3721                                   if (first.sendTime) {
3722                                     SendTimeRemaining(&first, TRUE);
3723                                   }
3724                                   SendToProgram("playother\n", &first);
3725                                   firstMove = FALSE;
3726                                 } else {
3727                                   firstMove = TRUE;
3728                                 }
3729                             }
3730                         } else if (gameMode == IcsPlayingBlack) {
3731                             if (!WhiteOnMove(forwardMostMove)) {
3732                                 if (first.sendTime) {
3733                                   if (first.useColors) {
3734                                     SendToProgram("white\n", &first);
3735                                   }
3736                                   SendTimeRemaining(&first, FALSE);
3737                                 }
3738                                 if (first.useColors) {
3739                                   SendToProgram("black\n", &first);
3740                                 }
3741                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3742                                 first.maybeThinking = TRUE;
3743                             } else {
3744                                 if (first.usePlayother) {
3745                                   if (first.sendTime) {
3746                                     SendTimeRemaining(&first, FALSE);
3747                                   }
3748                                   SendToProgram("playother\n", &first);
3749                                   firstMove = FALSE;
3750                                 } else {
3751                                   firstMove = TRUE;
3752                                 }
3753                             }
3754                         }
3755                     }
3756 #endif
3757                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3758                         /* Moves came from oldmoves or moves command
3759                            while we weren't doing anything else.
3760                            */
3761                         currentMove = forwardMostMove;
3762                         ClearHighlights();/*!!could figure this out*/
3763                         flipView = appData.flipView;
3764                         DrawPosition(TRUE, boards[currentMove]);
3765                         DisplayBothClocks();
3766                         snprintf(str, MSG_SIZ, "%s %s %s",
3767                                 gameInfo.white, _("vs."),  gameInfo.black);
3768                         DisplayTitle(str);
3769                         gameMode = IcsIdle;
3770                     } else {
3771                         /* Moves were history of an active game */
3772                         if (gameInfo.resultDetails != NULL) {
3773                             free(gameInfo.resultDetails);
3774                             gameInfo.resultDetails = NULL;
3775                         }
3776                     }
3777                     HistorySet(parseList, backwardMostMove,
3778                                forwardMostMove, currentMove-1);
3779                     DisplayMove(currentMove - 1);
3780                     if (started == STARTED_MOVES) next_out = i;
3781                     started = STARTED_NONE;
3782                     ics_getting_history = H_FALSE;
3783                     break;
3784
3785                   case STARTED_OBSERVE:
3786                     started = STARTED_NONE;
3787                     SendToICS(ics_prefix);
3788                     SendToICS("refresh\n");
3789                     break;
3790
3791                   default:
3792                     break;
3793                 }
3794                 if(bookHit) { // [HGM] book: simulate book reply
3795                     static char bookMove[MSG_SIZ]; // a bit generous?
3796
3797                     programStats.nodes = programStats.depth = programStats.time =
3798                     programStats.score = programStats.got_only_move = 0;
3799                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3800
3801                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3802                     strcat(bookMove, bookHit);
3803                     HandleMachineMove(bookMove, &first);
3804                 }
3805                 continue;
3806             }
3807
3808             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3809                  started == STARTED_HOLDINGS ||
3810                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3811                 /* Accumulate characters in move list or board */
3812                 parse[parse_pos++] = buf[i];
3813             }
3814
3815             /* Start of game messages.  Mostly we detect start of game
3816                when the first board image arrives.  On some versions
3817                of the ICS, though, we need to do a "refresh" after starting
3818                to observe in order to get the current board right away. */
3819             if (looking_at(buf, &i, "Adding game * to observation list")) {
3820                 started = STARTED_OBSERVE;
3821                 continue;
3822             }
3823
3824             /* Handle auto-observe */
3825             if (appData.autoObserve &&
3826                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3827                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3828                 char *player;
3829                 /* Choose the player that was highlighted, if any. */
3830                 if (star_match[0][0] == '\033' ||
3831                     star_match[1][0] != '\033') {
3832                     player = star_match[0];
3833                 } else {
3834                     player = star_match[2];
3835                 }
3836                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3837                         ics_prefix, StripHighlightAndTitle(player));
3838                 SendToICS(str);
3839
3840                 /* Save ratings from notify string */
3841                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3842                 player1Rating = string_to_rating(star_match[1]);
3843                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3844                 player2Rating = string_to_rating(star_match[3]);
3845
3846                 if (appData.debugMode)
3847                   fprintf(debugFP,
3848                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3849                           player1Name, player1Rating,
3850                           player2Name, player2Rating);
3851
3852                 continue;
3853             }
3854
3855             /* Deal with automatic examine mode after a game,
3856                and with IcsObserving -> IcsExamining transition */
3857             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3858                 looking_at(buf, &i, "has made you an examiner of game *")) {
3859
3860                 int gamenum = atoi(star_match[0]);
3861                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3862                     gamenum == ics_gamenum) {
3863                     /* We were already playing or observing this game;
3864                        no need to refetch history */
3865                     gameMode = IcsExamining;
3866                     if (pausing) {
3867                         pauseExamForwardMostMove = forwardMostMove;
3868                     } else if (currentMove < forwardMostMove) {
3869                         ForwardInner(forwardMostMove);
3870                     }
3871                 } else {
3872                     /* I don't think this case really can happen */
3873                     SendToICS(ics_prefix);
3874                     SendToICS("refresh\n");
3875                 }
3876                 continue;
3877             }
3878
3879             /* Error messages */
3880 //          if (ics_user_moved) {
3881             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3882                 if (looking_at(buf, &i, "Illegal move") ||
3883                     looking_at(buf, &i, "Not a legal move") ||
3884                     looking_at(buf, &i, "Your king is in check") ||
3885                     looking_at(buf, &i, "It isn't your turn") ||
3886                     looking_at(buf, &i, "It is not your move")) {
3887                     /* Illegal move */
3888                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3889                         currentMove = forwardMostMove-1;
3890                         DisplayMove(currentMove - 1); /* before DMError */
3891                         DrawPosition(FALSE, boards[currentMove]);
3892                         SwitchClocks(forwardMostMove-1); // [HGM] race
3893                         DisplayBothClocks();
3894                     }
3895                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3896                     ics_user_moved = 0;
3897                     continue;
3898                 }
3899             }
3900
3901             if (looking_at(buf, &i, "still have time") ||
3902                 looking_at(buf, &i, "not out of time") ||
3903                 looking_at(buf, &i, "either player is out of time") ||
3904                 looking_at(buf, &i, "has timeseal; checking")) {
3905                 /* We must have called his flag a little too soon */
3906                 whiteFlag = blackFlag = FALSE;
3907                 continue;
3908             }
3909
3910             if (looking_at(buf, &i, "added * seconds to") ||
3911                 looking_at(buf, &i, "seconds were added to")) {
3912                 /* Update the clocks */
3913                 SendToICS(ics_prefix);
3914                 SendToICS("refresh\n");
3915                 continue;
3916             }
3917
3918             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3919                 ics_clock_paused = TRUE;
3920                 StopClocks();
3921                 continue;
3922             }
3923
3924             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3925                 ics_clock_paused = FALSE;
3926                 StartClocks();
3927                 continue;
3928             }
3929
3930             /* Grab player ratings from the Creating: message.
3931                Note we have to check for the special case when
3932                the ICS inserts things like [white] or [black]. */
3933             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3934                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3935                 /* star_matches:
3936                    0    player 1 name (not necessarily white)
3937                    1    player 1 rating
3938                    2    empty, white, or black (IGNORED)
3939                    3    player 2 name (not necessarily black)
3940                    4    player 2 rating
3941
3942                    The names/ratings are sorted out when the game
3943                    actually starts (below).
3944                 */
3945                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3946                 player1Rating = string_to_rating(star_match[1]);
3947                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3948                 player2Rating = string_to_rating(star_match[4]);
3949
3950                 if (appData.debugMode)
3951                   fprintf(debugFP,
3952                           "Ratings from 'Creating:' %s %d, %s %d\n",
3953                           player1Name, player1Rating,
3954                           player2Name, player2Rating);
3955
3956                 continue;
3957             }
3958
3959             /* Improved generic start/end-of-game messages */
3960             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3961                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3962                 /* If tkind == 0: */
3963                 /* star_match[0] is the game number */
3964                 /*           [1] is the white player's name */
3965                 /*           [2] is the black player's name */
3966                 /* For end-of-game: */
3967                 /*           [3] is the reason for the game end */
3968                 /*           [4] is a PGN end game-token, preceded by " " */
3969                 /* For start-of-game: */
3970                 /*           [3] begins with "Creating" or "Continuing" */
3971                 /*           [4] is " *" or empty (don't care). */
3972                 int gamenum = atoi(star_match[0]);
3973                 char *whitename, *blackname, *why, *endtoken;
3974                 ChessMove endtype = EndOfFile;
3975
3976                 if (tkind == 0) {
3977                   whitename = star_match[1];
3978                   blackname = star_match[2];
3979                   why = star_match[3];
3980                   endtoken = star_match[4];
3981                 } else {
3982                   whitename = star_match[1];
3983                   blackname = star_match[3];
3984                   why = star_match[5];
3985                   endtoken = star_match[6];
3986                 }
3987
3988                 /* Game start messages */
3989                 if (strncmp(why, "Creating ", 9) == 0 ||
3990                     strncmp(why, "Continuing ", 11) == 0) {
3991                     gs_gamenum = gamenum;
3992                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3993                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3994                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3995 #if ZIPPY
3996                     if (appData.zippyPlay) {
3997                         ZippyGameStart(whitename, blackname);
3998                     }
3999 #endif /*ZIPPY*/
4000                     partnerBoardValid = FALSE; // [HGM] bughouse
4001                     continue;
4002                 }
4003
4004                 /* Game end messages */
4005                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4006                     ics_gamenum != gamenum) {
4007                     continue;
4008                 }
4009                 while (endtoken[0] == ' ') endtoken++;
4010                 switch (endtoken[0]) {
4011                   case '*':
4012                   default:
4013                     endtype = GameUnfinished;
4014                     break;
4015                   case '0':
4016                     endtype = BlackWins;
4017                     break;
4018                   case '1':
4019                     if (endtoken[1] == '/')
4020                       endtype = GameIsDrawn;
4021                     else
4022                       endtype = WhiteWins;
4023                     break;
4024                 }
4025                 GameEnds(endtype, why, GE_ICS);
4026 #if ZIPPY
4027                 if (appData.zippyPlay && first.initDone) {
4028                     ZippyGameEnd(endtype, why);
4029                     if (first.pr == NoProc) {
4030                       /* Start the next process early so that we'll
4031                          be ready for the next challenge */
4032                       StartChessProgram(&first);
4033                     }
4034                     /* Send "new" early, in case this command takes
4035                        a long time to finish, so that we'll be ready
4036                        for the next challenge. */
4037                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4038                     Reset(TRUE, TRUE);
4039                 }
4040 #endif /*ZIPPY*/
4041                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4042                 continue;
4043             }
4044
4045             if (looking_at(buf, &i, "Removing game * from observation") ||
4046                 looking_at(buf, &i, "no longer observing game *") ||
4047                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4048                 if (gameMode == IcsObserving &&
4049                     atoi(star_match[0]) == ics_gamenum)
4050                   {
4051                       /* icsEngineAnalyze */
4052                       if (appData.icsEngineAnalyze) {
4053                             ExitAnalyzeMode();
4054                             ModeHighlight();
4055                       }
4056                       StopClocks();
4057                       gameMode = IcsIdle;
4058                       ics_gamenum = -1;
4059                       ics_user_moved = FALSE;
4060                   }
4061                 continue;
4062             }
4063
4064             if (looking_at(buf, &i, "no longer examining game *")) {
4065                 if (gameMode == IcsExamining &&
4066                     atoi(star_match[0]) == ics_gamenum)
4067                   {
4068                       gameMode = IcsIdle;
4069                       ics_gamenum = -1;
4070                       ics_user_moved = FALSE;
4071                   }
4072                 continue;
4073             }
4074
4075             /* Advance leftover_start past any newlines we find,
4076                so only partial lines can get reparsed */
4077             if (looking_at(buf, &i, "\n")) {
4078                 prevColor = curColor;
4079                 if (curColor != ColorNormal) {
4080                     if (oldi > next_out) {
4081                         SendToPlayer(&buf[next_out], oldi - next_out);
4082                         next_out = oldi;
4083                     }
4084                     Colorize(ColorNormal, FALSE);
4085                     curColor = ColorNormal;
4086                 }
4087                 if (started == STARTED_BOARD) {
4088                     started = STARTED_NONE;
4089                     parse[parse_pos] = NULLCHAR;
4090                     ParseBoard12(parse);
4091                     ics_user_moved = 0;
4092
4093                     /* Send premove here */
4094                     if (appData.premove) {
4095                       char str[MSG_SIZ];
4096                       if (currentMove == 0 &&
4097                           gameMode == IcsPlayingWhite &&
4098                           appData.premoveWhite) {
4099                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4100                         if (appData.debugMode)
4101                           fprintf(debugFP, "Sending premove:\n");
4102                         SendToICS(str);
4103                       } else if (currentMove == 1 &&
4104                                  gameMode == IcsPlayingBlack &&
4105                                  appData.premoveBlack) {
4106                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4107                         if (appData.debugMode)
4108                           fprintf(debugFP, "Sending premove:\n");
4109                         SendToICS(str);
4110                       } else if (gotPremove) {
4111                         gotPremove = 0;
4112                         ClearPremoveHighlights();
4113                         if (appData.debugMode)
4114                           fprintf(debugFP, "Sending premove:\n");
4115                           UserMoveEvent(premoveFromX, premoveFromY,
4116                                         premoveToX, premoveToY,
4117                                         premovePromoChar);
4118                       }
4119                     }
4120
4121                     /* Usually suppress following prompt */
4122                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4123                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4124                         if (looking_at(buf, &i, "*% ")) {
4125                             savingComment = FALSE;
4126                             suppressKibitz = 0;
4127                         }
4128                     }
4129                     next_out = i;
4130                 } else if (started == STARTED_HOLDINGS) {
4131                     int gamenum;
4132                     char new_piece[MSG_SIZ];
4133                     started = STARTED_NONE;
4134                     parse[parse_pos] = NULLCHAR;
4135                     if (appData.debugMode)
4136                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4137                                                         parse, currentMove);
4138                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4139                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4140                         if (gameInfo.variant == VariantNormal) {
4141                           /* [HGM] We seem to switch variant during a game!
4142                            * Presumably no holdings were displayed, so we have
4143                            * to move the position two files to the right to
4144                            * create room for them!
4145                            */
4146                           VariantClass newVariant;
4147                           switch(gameInfo.boardWidth) { // base guess on board width
4148                                 case 9:  newVariant = VariantShogi; break;
4149                                 case 10: newVariant = VariantGreat; break;
4150                                 default: newVariant = VariantCrazyhouse; break;
4151                           }
4152                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4153                           /* Get a move list just to see the header, which
4154                              will tell us whether this is really bug or zh */
4155                           if (ics_getting_history == H_FALSE) {
4156                             ics_getting_history = H_REQUESTED;
4157                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4158                             SendToICS(str);
4159                           }
4160                         }
4161                         new_piece[0] = NULLCHAR;
4162                         sscanf(parse, "game %d white [%s black [%s <- %s",
4163                                &gamenum, white_holding, black_holding,
4164                                new_piece);
4165                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4166                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4167                         /* [HGM] copy holdings to board holdings area */
4168                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4169                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4170                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4171 #if ZIPPY
4172                         if (appData.zippyPlay && first.initDone) {
4173                             ZippyHoldings(white_holding, black_holding,
4174                                           new_piece);
4175                         }
4176 #endif /*ZIPPY*/
4177                         if (tinyLayout || smallLayout) {
4178                             char wh[16], bh[16];
4179                             PackHolding(wh, white_holding);
4180                             PackHolding(bh, black_holding);
4181                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4182                                     gameInfo.white, gameInfo.black);
4183                         } else {
4184                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4185                                     gameInfo.white, white_holding, _("vs."),
4186                                     gameInfo.black, black_holding);
4187                         }
4188                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4189                         DrawPosition(FALSE, boards[currentMove]);
4190                         DisplayTitle(str);
4191                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4192                         sscanf(parse, "game %d white [%s black [%s <- %s",
4193                                &gamenum, white_holding, black_holding,
4194                                new_piece);
4195                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4196                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4197                         /* [HGM] copy holdings to partner-board holdings area */
4198                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4199                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4200                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4201                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4202                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4203                       }
4204                     }
4205                     /* Suppress following prompt */
4206                     if (looking_at(buf, &i, "*% ")) {
4207                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4208                         savingComment = FALSE;
4209                         suppressKibitz = 0;
4210                     }
4211                     next_out = i;
4212                 }
4213                 continue;
4214             }
4215
4216             i++;                /* skip unparsed character and loop back */
4217         }
4218
4219         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4220 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4221 //          SendToPlayer(&buf[next_out], i - next_out);
4222             started != STARTED_HOLDINGS && leftover_start > next_out) {
4223             SendToPlayer(&buf[next_out], leftover_start - next_out);
4224             next_out = i;
4225         }
4226
4227         leftover_len = buf_len - leftover_start;
4228         /* if buffer ends with something we couldn't parse,
4229            reparse it after appending the next read */
4230
4231     } else if (count == 0) {
4232         RemoveInputSource(isr);
4233         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4234     } else {
4235         DisplayFatalError(_("Error reading from ICS"), error, 1);
4236     }
4237 }
4238
4239
4240 /* Board style 12 looks like this:
4241
4242    <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
4243
4244  * The "<12> " is stripped before it gets to this routine.  The two
4245  * trailing 0's (flip state and clock ticking) are later addition, and
4246  * some chess servers may not have them, or may have only the first.
4247  * Additional trailing fields may be added in the future.
4248  */
4249
4250 #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"
4251
4252 #define RELATION_OBSERVING_PLAYED    0
4253 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4254 #define RELATION_PLAYING_MYMOVE      1
4255 #define RELATION_PLAYING_NOTMYMOVE  -1
4256 #define RELATION_EXAMINING           2
4257 #define RELATION_ISOLATED_BOARD     -3
4258 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4259
4260 void
4261 ParseBoard12 (char *string)
4262 {
4263 #if ZIPPY
4264     int i, takeback;
4265     char *bookHit = NULL; // [HGM] book
4266 #endif
4267     GameMode newGameMode;
4268     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4269     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4270     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4271     char to_play, board_chars[200];
4272     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4273     char black[32], white[32];
4274     Board board;
4275     int prevMove = currentMove;
4276     int ticking = 2;
4277     ChessMove moveType;
4278     int fromX, fromY, toX, toY;
4279     char promoChar;
4280     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4281     Boolean weird = FALSE, reqFlag = FALSE;
4282
4283     fromX = fromY = toX = toY = -1;
4284
4285     newGame = FALSE;
4286
4287     if (appData.debugMode)
4288       fprintf(debugFP, "Parsing board: %s\n", string);
4289
4290     move_str[0] = NULLCHAR;
4291     elapsed_time[0] = NULLCHAR;
4292     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4293         int  i = 0, j;
4294         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4295             if(string[i] == ' ') { ranks++; files = 0; }
4296             else files++;
4297             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4298             i++;
4299         }
4300         for(j = 0; j <i; j++) board_chars[j] = string[j];
4301         board_chars[i] = '\0';
4302         string += i + 1;
4303     }
4304     n = sscanf(string, PATTERN, &to_play, &double_push,
4305                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4306                &gamenum, white, black, &relation, &basetime, &increment,
4307                &white_stren, &black_stren, &white_time, &black_time,
4308                &moveNum, str, elapsed_time, move_str, &ics_flip,
4309                &ticking);
4310
4311     if (n < 21) {
4312         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4313         DisplayError(str, 0);
4314         return;
4315     }
4316
4317     /* Convert the move number to internal form */
4318     moveNum = (moveNum - 1) * 2;
4319     if (to_play == 'B') moveNum++;
4320     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4321       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4322                         0, 1);
4323       return;
4324     }
4325
4326     switch (relation) {
4327       case RELATION_OBSERVING_PLAYED:
4328       case RELATION_OBSERVING_STATIC:
4329         if (gamenum == -1) {
4330             /* Old ICC buglet */
4331             relation = RELATION_OBSERVING_STATIC;
4332         }
4333         newGameMode = IcsObserving;
4334         break;
4335       case RELATION_PLAYING_MYMOVE:
4336       case RELATION_PLAYING_NOTMYMOVE:
4337         newGameMode =
4338           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4339             IcsPlayingWhite : IcsPlayingBlack;
4340         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4341         break;
4342       case RELATION_EXAMINING:
4343         newGameMode = IcsExamining;
4344         break;
4345       case RELATION_ISOLATED_BOARD:
4346       default:
4347         /* Just display this board.  If user was doing something else,
4348            we will forget about it until the next board comes. */
4349         newGameMode = IcsIdle;
4350         break;
4351       case RELATION_STARTING_POSITION:
4352         newGameMode = gameMode;
4353         break;
4354     }
4355
4356     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4357         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4358          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4359       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4360       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4361       static int lastBgGame = -1;
4362       char *toSqr;
4363       for (k = 0; k < ranks; k++) {
4364         for (j = 0; j < files; j++)
4365           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4366         if(gameInfo.holdingsWidth > 1) {
4367              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4368              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4369         }
4370       }
4371       CopyBoard(partnerBoard, board);
4372       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4373         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4374         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4375       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4376       if(toSqr = strchr(str, '-')) {
4377         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4378         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4379       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4380       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4381       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4382       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4383       if(twoBoards) {
4384           DisplayWhiteClock(white_time*fac, to_play == 'W');
4385           DisplayBlackClock(black_time*fac, to_play != 'W');
4386           activePartner = to_play;
4387           if(gamenum != lastBgGame) {
4388               char buf[MSG_SIZ];
4389               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4390               DisplayTitle(buf);
4391           }
4392           lastBgGame = gamenum;
4393           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4394                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4395       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4396                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4397       if(!twoBoards) DisplayMessage(partnerStatus, "");
4398         partnerBoardValid = TRUE;
4399       return;
4400     }
4401
4402     if(appData.dualBoard && appData.bgObserve) {
4403         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4404             SendToICS(ics_prefix), SendToICS("pobserve\n");
4405         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4406             char buf[MSG_SIZ];
4407             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4408             SendToICS(buf);
4409         }
4410     }
4411
4412     /* Modify behavior for initial board display on move listing
4413        of wild games.
4414        */
4415     switch (ics_getting_history) {
4416       case H_FALSE:
4417       case H_REQUESTED:
4418         break;
4419       case H_GOT_REQ_HEADER:
4420       case H_GOT_UNREQ_HEADER:
4421         /* This is the initial position of the current game */
4422         gamenum = ics_gamenum;
4423         moveNum = 0;            /* old ICS bug workaround */
4424         if (to_play == 'B') {
4425           startedFromSetupPosition = TRUE;
4426           blackPlaysFirst = TRUE;
4427           moveNum = 1;
4428           if (forwardMostMove == 0) forwardMostMove = 1;
4429           if (backwardMostMove == 0) backwardMostMove = 1;
4430           if (currentMove == 0) currentMove = 1;
4431         }
4432         newGameMode = gameMode;
4433         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4434         break;
4435       case H_GOT_UNWANTED_HEADER:
4436         /* This is an initial board that we don't want */
4437         return;
4438       case H_GETTING_MOVES:
4439         /* Should not happen */
4440         DisplayError(_("Error gathering move list: extra board"), 0);
4441         ics_getting_history = H_FALSE;
4442         return;
4443     }
4444
4445    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4446                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4447                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4448      /* [HGM] We seem to have switched variant unexpectedly
4449       * Try to guess new variant from board size
4450       */
4451           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4452           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4453           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4454           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4455           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4456           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4457           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4458           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4459           /* Get a move list just to see the header, which
4460              will tell us whether this is really bug or zh */
4461           if (ics_getting_history == H_FALSE) {
4462             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4463             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4464             SendToICS(str);
4465           }
4466     }
4467
4468     /* Take action if this is the first board of a new game, or of a
4469        different game than is currently being displayed.  */
4470     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4471         relation == RELATION_ISOLATED_BOARD) {
4472
4473         /* Forget the old game and get the history (if any) of the new one */
4474         if (gameMode != BeginningOfGame) {
4475           Reset(TRUE, TRUE);
4476         }
4477         newGame = TRUE;
4478         if (appData.autoRaiseBoard) BoardToTop();
4479         prevMove = -3;
4480         if (gamenum == -1) {
4481             newGameMode = IcsIdle;
4482         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4483                    appData.getMoveList && !reqFlag) {
4484             /* Need to get game history */
4485             ics_getting_history = H_REQUESTED;
4486             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4487             SendToICS(str);
4488         }
4489
4490         /* Initially flip the board to have black on the bottom if playing
4491            black or if the ICS flip flag is set, but let the user change
4492            it with the Flip View button. */
4493         flipView = appData.autoFlipView ?
4494           (newGameMode == IcsPlayingBlack) || ics_flip :
4495           appData.flipView;
4496
4497         /* Done with values from previous mode; copy in new ones */
4498         gameMode = newGameMode;
4499         ModeHighlight();
4500         ics_gamenum = gamenum;
4501         if (gamenum == gs_gamenum) {
4502             int klen = strlen(gs_kind);
4503             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4504             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4505             gameInfo.event = StrSave(str);
4506         } else {
4507             gameInfo.event = StrSave("ICS game");
4508         }
4509         gameInfo.site = StrSave(appData.icsHost);
4510         gameInfo.date = PGNDate();
4511         gameInfo.round = StrSave("-");
4512         gameInfo.white = StrSave(white);
4513         gameInfo.black = StrSave(black);
4514         timeControl = basetime * 60 * 1000;
4515         timeControl_2 = 0;
4516         timeIncrement = increment * 1000;
4517         movesPerSession = 0;
4518         gameInfo.timeControl = TimeControlTagValue();
4519         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4520   if (appData.debugMode) {
4521     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4522     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4523     setbuf(debugFP, NULL);
4524   }
4525
4526         gameInfo.outOfBook = NULL;
4527
4528         /* Do we have the ratings? */
4529         if (strcmp(player1Name, white) == 0 &&
4530             strcmp(player2Name, black) == 0) {
4531             if (appData.debugMode)
4532               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4533                       player1Rating, player2Rating);
4534             gameInfo.whiteRating = player1Rating;
4535             gameInfo.blackRating = player2Rating;
4536         } else if (strcmp(player2Name, white) == 0 &&
4537                    strcmp(player1Name, black) == 0) {
4538             if (appData.debugMode)
4539               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4540                       player2Rating, player1Rating);
4541             gameInfo.whiteRating = player2Rating;
4542             gameInfo.blackRating = player1Rating;
4543         }
4544         player1Name[0] = player2Name[0] = NULLCHAR;
4545
4546         /* Silence shouts if requested */
4547         if (appData.quietPlay &&
4548             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4549             SendToICS(ics_prefix);
4550             SendToICS("set shout 0\n");
4551         }
4552     }
4553
4554     /* Deal with midgame name changes */
4555     if (!newGame) {
4556         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4557             if (gameInfo.white) free(gameInfo.white);
4558             gameInfo.white = StrSave(white);
4559         }
4560         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4561             if (gameInfo.black) free(gameInfo.black);
4562             gameInfo.black = StrSave(black);
4563         }
4564     }
4565
4566     /* Throw away game result if anything actually changes in examine mode */
4567     if (gameMode == IcsExamining && !newGame) {
4568         gameInfo.result = GameUnfinished;
4569         if (gameInfo.resultDetails != NULL) {
4570             free(gameInfo.resultDetails);
4571             gameInfo.resultDetails = NULL;
4572         }
4573     }
4574
4575     /* In pausing && IcsExamining mode, we ignore boards coming
4576        in if they are in a different variation than we are. */
4577     if (pauseExamInvalid) return;
4578     if (pausing && gameMode == IcsExamining) {
4579         if (moveNum <= pauseExamForwardMostMove) {
4580             pauseExamInvalid = TRUE;
4581             forwardMostMove = pauseExamForwardMostMove;
4582             return;
4583         }
4584     }
4585
4586   if (appData.debugMode) {
4587     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4588   }
4589     /* Parse the board */
4590     for (k = 0; k < ranks; k++) {
4591       for (j = 0; j < files; j++)
4592         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4593       if(gameInfo.holdingsWidth > 1) {
4594            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4595            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4596       }
4597     }
4598     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4599       board[5][BOARD_RGHT+1] = WhiteAngel;
4600       board[6][BOARD_RGHT+1] = WhiteMarshall;
4601       board[1][0] = BlackMarshall;
4602       board[2][0] = BlackAngel;
4603       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4604     }
4605     CopyBoard(boards[moveNum], board);
4606     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4607     if (moveNum == 0) {
4608         startedFromSetupPosition =
4609           !CompareBoards(board, initialPosition);
4610         if(startedFromSetupPosition)
4611             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4612     }
4613
4614     /* [HGM] Set castling rights. Take the outermost Rooks,
4615        to make it also work for FRC opening positions. Note that board12
4616        is really defective for later FRC positions, as it has no way to
4617        indicate which Rook can castle if they are on the same side of King.
4618        For the initial position we grant rights to the outermost Rooks,
4619        and remember thos rights, and we then copy them on positions
4620        later in an FRC game. This means WB might not recognize castlings with
4621        Rooks that have moved back to their original position as illegal,
4622        but in ICS mode that is not its job anyway.
4623     */
4624     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4625     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4626
4627         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4628             if(board[0][i] == WhiteRook) j = i;
4629         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4630         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4631             if(board[0][i] == WhiteRook) j = i;
4632         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4633         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4634             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4635         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4636         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4637             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4638         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4639
4640         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4641         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4642         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4643             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4644         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4645             if(board[BOARD_HEIGHT-1][k] == bKing)
4646                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4647         if(gameInfo.variant == VariantTwoKings) {
4648             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4649             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4650             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4651         }
4652     } else { int r;
4653         r = boards[moveNum][CASTLING][0] = initialRights[0];
4654         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4655         r = boards[moveNum][CASTLING][1] = initialRights[1];
4656         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4657         r = boards[moveNum][CASTLING][3] = initialRights[3];
4658         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4659         r = boards[moveNum][CASTLING][4] = initialRights[4];
4660         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4661         /* wildcastle kludge: always assume King has rights */
4662         r = boards[moveNum][CASTLING][2] = initialRights[2];
4663         r = boards[moveNum][CASTLING][5] = initialRights[5];
4664     }
4665     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4666     boards[moveNum][EP_STATUS] = EP_NONE;
4667     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4668     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4669     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4670
4671
4672     if (ics_getting_history == H_GOT_REQ_HEADER ||
4673         ics_getting_history == H_GOT_UNREQ_HEADER) {
4674         /* This was an initial position from a move list, not
4675            the current position */
4676         return;
4677     }
4678
4679     /* Update currentMove and known move number limits */
4680     newMove = newGame || moveNum > forwardMostMove;
4681
4682     if (newGame) {
4683         forwardMostMove = backwardMostMove = currentMove = moveNum;
4684         if (gameMode == IcsExamining && moveNum == 0) {
4685           /* Workaround for ICS limitation: we are not told the wild
4686              type when starting to examine a game.  But if we ask for
4687              the move list, the move list header will tell us */
4688             ics_getting_history = H_REQUESTED;
4689             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4690             SendToICS(str);
4691         }
4692     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4693                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4694 #if ZIPPY
4695         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4696         /* [HGM] applied this also to an engine that is silently watching        */
4697         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4698             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4699             gameInfo.variant == currentlyInitializedVariant) {
4700           takeback = forwardMostMove - moveNum;
4701           for (i = 0; i < takeback; i++) {
4702             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4703             SendToProgram("undo\n", &first);
4704           }
4705         }
4706 #endif
4707
4708         forwardMostMove = moveNum;
4709         if (!pausing || currentMove > forwardMostMove)
4710           currentMove = forwardMostMove;
4711     } else {
4712         /* New part of history that is not contiguous with old part */
4713         if (pausing && gameMode == IcsExamining) {
4714             pauseExamInvalid = TRUE;
4715             forwardMostMove = pauseExamForwardMostMove;
4716             return;
4717         }
4718         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4719 #if ZIPPY
4720             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4721                 // [HGM] when we will receive the move list we now request, it will be
4722                 // fed to the engine from the first move on. So if the engine is not
4723                 // in the initial position now, bring it there.
4724                 InitChessProgram(&first, 0);
4725             }
4726 #endif
4727             ics_getting_history = H_REQUESTED;
4728             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4729             SendToICS(str);
4730         }
4731         forwardMostMove = backwardMostMove = currentMove = moveNum;
4732     }
4733
4734     /* Update the clocks */
4735     if (strchr(elapsed_time, '.')) {
4736       /* Time is in ms */
4737       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4738       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4739     } else {
4740       /* Time is in seconds */
4741       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4742       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4743     }
4744
4745
4746 #if ZIPPY
4747     if (appData.zippyPlay && newGame &&
4748         gameMode != IcsObserving && gameMode != IcsIdle &&
4749         gameMode != IcsExamining)
4750       ZippyFirstBoard(moveNum, basetime, increment);
4751 #endif
4752
4753     /* Put the move on the move list, first converting
4754        to canonical algebraic form. */
4755     if (moveNum > 0) {
4756   if (appData.debugMode) {
4757     int f = forwardMostMove;
4758     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4759             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4760             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4761     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4762     fprintf(debugFP, "moveNum = %d\n", moveNum);
4763     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4764     setbuf(debugFP, NULL);
4765   }
4766         if (moveNum <= backwardMostMove) {
4767             /* We don't know what the board looked like before
4768                this move.  Punt. */
4769           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4770             strcat(parseList[moveNum - 1], " ");
4771             strcat(parseList[moveNum - 1], elapsed_time);
4772             moveList[moveNum - 1][0] = NULLCHAR;
4773         } else if (strcmp(move_str, "none") == 0) {
4774             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4775             /* Again, we don't know what the board looked like;
4776                this is really the start of the game. */
4777             parseList[moveNum - 1][0] = NULLCHAR;
4778             moveList[moveNum - 1][0] = NULLCHAR;
4779             backwardMostMove = moveNum;
4780             startedFromSetupPosition = TRUE;
4781             fromX = fromY = toX = toY = -1;
4782         } else {
4783           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4784           //                 So we parse the long-algebraic move string in stead of the SAN move
4785           int valid; char buf[MSG_SIZ], *prom;
4786
4787           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4788                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4789           // str looks something like "Q/a1-a2"; kill the slash
4790           if(str[1] == '/')
4791             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4792           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4793           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4794                 strcat(buf, prom); // long move lacks promo specification!
4795           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4796                 if(appData.debugMode)
4797                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4798                 safeStrCpy(move_str, buf, MSG_SIZ);
4799           }
4800           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4801                                 &fromX, &fromY, &toX, &toY, &promoChar)
4802                || ParseOneMove(buf, moveNum - 1, &moveType,
4803                                 &fromX, &fromY, &toX, &toY, &promoChar);
4804           // end of long SAN patch
4805           if (valid) {
4806             (void) CoordsToAlgebraic(boards[moveNum - 1],
4807                                      PosFlags(moveNum - 1),
4808                                      fromY, fromX, toY, toX, promoChar,
4809                                      parseList[moveNum-1]);
4810             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4811               case MT_NONE:
4812               case MT_STALEMATE:
4813               default:
4814                 break;
4815               case MT_CHECK:
4816                 if(!IS_SHOGI(gameInfo.variant))
4817                     strcat(parseList[moveNum - 1], "+");
4818                 break;
4819               case MT_CHECKMATE:
4820               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4821                 strcat(parseList[moveNum - 1], "#");
4822                 break;
4823             }
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             /* currentMoveString is set as a side-effect of ParseOneMove */
4827             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4828             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4829             strcat(moveList[moveNum - 1], "\n");
4830
4831             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4832                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4833               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4834                 ChessSquare old, new = boards[moveNum][k][j];
4835                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4836                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4837                   if(old == new) continue;
4838                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4839                   else if(new == WhiteWazir || new == BlackWazir) {
4840                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4841                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4842                       else boards[moveNum][k][j] = old; // preserve type of Gold
4843                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4844                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4845               }
4846           } else {
4847             /* Move from ICS was illegal!?  Punt. */
4848             if (appData.debugMode) {
4849               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4850               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4851             }
4852             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4853             strcat(parseList[moveNum - 1], " ");
4854             strcat(parseList[moveNum - 1], elapsed_time);
4855             moveList[moveNum - 1][0] = NULLCHAR;
4856             fromX = fromY = toX = toY = -1;
4857           }
4858         }
4859   if (appData.debugMode) {
4860     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4861     setbuf(debugFP, NULL);
4862   }
4863
4864 #if ZIPPY
4865         /* Send move to chess program (BEFORE animating it). */
4866         if (appData.zippyPlay && !newGame && newMove &&
4867            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4868
4869             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4870                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4871                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4872                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4873                             move_str);
4874                     DisplayError(str, 0);
4875                 } else {
4876                     if (first.sendTime) {
4877                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4878                     }
4879                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4880                     if (firstMove && !bookHit) {
4881                         firstMove = FALSE;
4882                         if (first.useColors) {
4883                           SendToProgram(gameMode == IcsPlayingWhite ?
4884                                         "white\ngo\n" :
4885                                         "black\ngo\n", &first);
4886                         } else {
4887                           SendToProgram("go\n", &first);
4888                         }
4889                         first.maybeThinking = TRUE;
4890                     }
4891                 }
4892             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4893               if (moveList[moveNum - 1][0] == NULLCHAR) {
4894                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4895                 DisplayError(str, 0);
4896               } else {
4897                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4898                 SendMoveToProgram(moveNum - 1, &first);
4899               }
4900             }
4901         }
4902 #endif
4903     }
4904
4905     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4906         /* If move comes from a remote source, animate it.  If it
4907            isn't remote, it will have already been animated. */
4908         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4909             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4910         }
4911         if (!pausing && appData.highlightLastMove) {
4912             SetHighlights(fromX, fromY, toX, toY);
4913         }
4914     }
4915
4916     /* Start the clocks */
4917     whiteFlag = blackFlag = FALSE;
4918     appData.clockMode = !(basetime == 0 && increment == 0);
4919     if (ticking == 0) {
4920       ics_clock_paused = TRUE;
4921       StopClocks();
4922     } else if (ticking == 1) {
4923       ics_clock_paused = FALSE;
4924     }
4925     if (gameMode == IcsIdle ||
4926         relation == RELATION_OBSERVING_STATIC ||
4927         relation == RELATION_EXAMINING ||
4928         ics_clock_paused)
4929       DisplayBothClocks();
4930     else
4931       StartClocks();
4932
4933     /* Display opponents and material strengths */
4934     if (gameInfo.variant != VariantBughouse &&
4935         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4936         if (tinyLayout || smallLayout) {
4937             if(gameInfo.variant == VariantNormal)
4938               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4939                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4940                     basetime, increment);
4941             else
4942               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4943                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4944                     basetime, increment, (int) gameInfo.variant);
4945         } else {
4946             if(gameInfo.variant == VariantNormal)
4947               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4948                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4949                     basetime, increment);
4950             else
4951               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4952                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4953                     basetime, increment, VariantName(gameInfo.variant));
4954         }
4955         DisplayTitle(str);
4956   if (appData.debugMode) {
4957     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4958   }
4959     }
4960
4961
4962     /* Display the board */
4963     if (!pausing && !appData.noGUI) {
4964
4965       if (appData.premove)
4966           if (!gotPremove ||
4967              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4968              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4969               ClearPremoveHighlights();
4970
4971       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4972         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4973       DrawPosition(j, boards[currentMove]);
4974
4975       DisplayMove(moveNum - 1);
4976       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4977             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4978               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4979         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4980       }
4981     }
4982
4983     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4984 #if ZIPPY
4985     if(bookHit) { // [HGM] book: simulate book reply
4986         static char bookMove[MSG_SIZ]; // a bit generous?
4987
4988         programStats.nodes = programStats.depth = programStats.time =
4989         programStats.score = programStats.got_only_move = 0;
4990         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4991
4992         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4993         strcat(bookMove, bookHit);
4994         HandleMachineMove(bookMove, &first);
4995     }
4996 #endif
4997 }
4998
4999 void
5000 GetMoveListEvent ()
5001 {
5002     char buf[MSG_SIZ];
5003     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5004         ics_getting_history = H_REQUESTED;
5005         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5006         SendToICS(buf);
5007     }
5008 }
5009
5010 void
5011 SendToBoth (char *msg)
5012 {   // to make it easy to keep two engines in step in dual analysis
5013     SendToProgram(msg, &first);
5014     if(second.analyzing) SendToProgram(msg, &second);
5015 }
5016
5017 void
5018 AnalysisPeriodicEvent (int force)
5019 {
5020     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5021          && !force) || !appData.periodicUpdates)
5022       return;
5023
5024     /* Send . command to Crafty to collect stats */
5025     SendToBoth(".\n");
5026
5027     /* Don't send another until we get a response (this makes
5028        us stop sending to old Crafty's which don't understand
5029        the "." command (sending illegal cmds resets node count & time,
5030        which looks bad)) */
5031     programStats.ok_to_send = 0;
5032 }
5033
5034 void
5035 ics_update_width (int new_width)
5036 {
5037         ics_printf("set width %d\n", new_width);
5038 }
5039
5040 void
5041 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5042 {
5043     char buf[MSG_SIZ];
5044
5045     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5046         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5047             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5048             SendToProgram(buf, cps);
5049             return;
5050         }
5051         // null move in variant where engine does not understand it (for analysis purposes)
5052         SendBoard(cps, moveNum + 1); // send position after move in stead.
5053         return;
5054     }
5055     if (cps->useUsermove) {
5056       SendToProgram("usermove ", cps);
5057     }
5058     if (cps->useSAN) {
5059       char *space;
5060       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5061         int len = space - parseList[moveNum];
5062         memcpy(buf, parseList[moveNum], len);
5063         buf[len++] = '\n';
5064         buf[len] = NULLCHAR;
5065       } else {
5066         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5067       }
5068       SendToProgram(buf, cps);
5069     } else {
5070       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5071         AlphaRank(moveList[moveNum], 4);
5072         SendToProgram(moveList[moveNum], cps);
5073         AlphaRank(moveList[moveNum], 4); // and back
5074       } else
5075       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5076        * the engine. It would be nice to have a better way to identify castle
5077        * moves here. */
5078       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5079                                                                          && cps->useOOCastle) {
5080         int fromX = moveList[moveNum][0] - AAA;
5081         int fromY = moveList[moveNum][1] - ONE;
5082         int toX = moveList[moveNum][2] - AAA;
5083         int toY = moveList[moveNum][3] - ONE;
5084         if((boards[moveNum][fromY][fromX] == WhiteKing
5085             && boards[moveNum][toY][toX] == WhiteRook)
5086            || (boards[moveNum][fromY][fromX] == BlackKing
5087                && boards[moveNum][toY][toX] == BlackRook)) {
5088           if(toX > fromX) SendToProgram("O-O\n", cps);
5089           else SendToProgram("O-O-O\n", cps);
5090         }
5091         else SendToProgram(moveList[moveNum], cps);
5092       } else
5093       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5094           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5095                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5096                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5097                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5098           SendToProgram(buf, cps);
5099       } else
5100       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5101         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5102           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5103           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5104                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5105         } else
5106           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5107                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5108         SendToProgram(buf, cps);
5109       }
5110       else SendToProgram(moveList[moveNum], cps);
5111       /* End of additions by Tord */
5112     }
5113
5114     /* [HGM] setting up the opening has brought engine in force mode! */
5115     /*       Send 'go' if we are in a mode where machine should play. */
5116     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5117         (gameMode == TwoMachinesPlay   ||
5118 #if ZIPPY
5119          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5120 #endif
5121          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5122         SendToProgram("go\n", cps);
5123   if (appData.debugMode) {
5124     fprintf(debugFP, "(extra)\n");
5125   }
5126     }
5127     setboardSpoiledMachineBlack = 0;
5128 }
5129
5130 void
5131 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5132 {
5133     char user_move[MSG_SIZ];
5134     char suffix[4];
5135
5136     if(gameInfo.variant == VariantSChess && promoChar) {
5137         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5138         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5139     } else suffix[0] = NULLCHAR;
5140
5141     switch (moveType) {
5142       default:
5143         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5144                 (int)moveType, fromX, fromY, toX, toY);
5145         DisplayError(user_move + strlen("say "), 0);
5146         break;
5147       case WhiteKingSideCastle:
5148       case BlackKingSideCastle:
5149       case WhiteQueenSideCastleWild:
5150       case BlackQueenSideCastleWild:
5151       /* PUSH Fabien */
5152       case WhiteHSideCastleFR:
5153       case BlackHSideCastleFR:
5154       /* POP Fabien */
5155         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5156         break;
5157       case WhiteQueenSideCastle:
5158       case BlackQueenSideCastle:
5159       case WhiteKingSideCastleWild:
5160       case BlackKingSideCastleWild:
5161       /* PUSH Fabien */
5162       case WhiteASideCastleFR:
5163       case BlackASideCastleFR:
5164       /* POP Fabien */
5165         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5166         break;
5167       case WhiteNonPromotion:
5168       case BlackNonPromotion:
5169         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5170         break;
5171       case WhitePromotion:
5172       case BlackPromotion:
5173         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5174            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5175           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5177                 PieceToChar(WhiteFerz));
5178         else if(gameInfo.variant == VariantGreat)
5179           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5180                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5181                 PieceToChar(WhiteMan));
5182         else
5183           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5184                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5185                 promoChar);
5186         break;
5187       case WhiteDrop:
5188       case BlackDrop:
5189       drop:
5190         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5191                  ToUpper(PieceToChar((ChessSquare) fromX)),
5192                  AAA + toX, ONE + toY);
5193         break;
5194       case IllegalMove:  /* could be a variant we don't quite understand */
5195         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5196       case NormalMove:
5197       case WhiteCapturesEnPassant:
5198       case BlackCapturesEnPassant:
5199         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5200                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5201         break;
5202     }
5203     SendToICS(user_move);
5204     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5205         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5206 }
5207
5208 void
5209 UploadGameEvent ()
5210 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5211     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5212     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5213     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5214       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5215       return;
5216     }
5217     if(gameMode != IcsExamining) { // is this ever not the case?
5218         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5219
5220         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5221           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5222         } else { // on FICS we must first go to general examine mode
5223           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5224         }
5225         if(gameInfo.variant != VariantNormal) {
5226             // try figure out wild number, as xboard names are not always valid on ICS
5227             for(i=1; i<=36; i++) {
5228               snprintf(buf, MSG_SIZ, "wild/%d", i);
5229                 if(StringToVariant(buf) == gameInfo.variant) break;
5230             }
5231             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5232             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5233             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5234         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5235         SendToICS(ics_prefix);
5236         SendToICS(buf);
5237         if(startedFromSetupPosition || backwardMostMove != 0) {
5238           fen = PositionToFEN(backwardMostMove, NULL, 1);
5239           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5240             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5241             SendToICS(buf);
5242           } else { // FICS: everything has to set by separate bsetup commands
5243             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5244             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5245             SendToICS(buf);
5246             if(!WhiteOnMove(backwardMostMove)) {
5247                 SendToICS("bsetup tomove black\n");
5248             }
5249             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5250             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5251             SendToICS(buf);
5252             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5253             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5254             SendToICS(buf);
5255             i = boards[backwardMostMove][EP_STATUS];
5256             if(i >= 0) { // set e.p.
5257               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5258                 SendToICS(buf);
5259             }
5260             bsetup++;
5261           }
5262         }
5263       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5264             SendToICS("bsetup done\n"); // switch to normal examining.
5265     }
5266     for(i = backwardMostMove; i<last; i++) {
5267         char buf[20];
5268         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5269         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5270             int len = strlen(moveList[i]);
5271             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5272             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5273         }
5274         SendToICS(buf);
5275     }
5276     SendToICS(ics_prefix);
5277     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5278 }
5279
5280 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5281
5282 void
5283 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5284 {
5285     if (rf == DROP_RANK) {
5286       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5287       sprintf(move, "%c@%c%c\n",
5288                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5289     } else {
5290         if (promoChar == 'x' || promoChar == NULLCHAR) {
5291           sprintf(move, "%c%c%c%c\n",
5292                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5293           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5294         } else {
5295             sprintf(move, "%c%c%c%c%c\n",
5296                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5297         }
5298     }
5299 }
5300
5301 void
5302 ProcessICSInitScript (FILE *f)
5303 {
5304     char buf[MSG_SIZ];
5305
5306     while (fgets(buf, MSG_SIZ, f)) {
5307         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5308     }
5309
5310     fclose(f);
5311 }
5312
5313
5314 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5315 int dragging;
5316 static ClickType lastClickType;
5317
5318 int
5319 Partner (ChessSquare *p)
5320 { // change piece into promotion partner if one shogi-promotes to the other
5321   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5322   ChessSquare partner;
5323   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5324   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5325   *p = partner;
5326   return 1;
5327 }
5328
5329 void
5330 Sweep (int step)
5331 {
5332     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5333     static int toggleFlag;
5334     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5335     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5336     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5337     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5338     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5339     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5340     do {
5341         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5342         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5343         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5344         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5345         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5346         if(!step) step = -1;
5347     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5348             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5349             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5350             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5351     if(toX >= 0) {
5352         int victim = boards[currentMove][toY][toX];
5353         boards[currentMove][toY][toX] = promoSweep;
5354         DrawPosition(FALSE, boards[currentMove]);
5355         boards[currentMove][toY][toX] = victim;
5356     } else
5357     ChangeDragPiece(promoSweep);
5358 }
5359
5360 int
5361 PromoScroll (int x, int y)
5362 {
5363   int step = 0;
5364
5365   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5366   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5367   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5368   if(!step) return FALSE;
5369   lastX = x; lastY = y;
5370   if((promoSweep < BlackPawn) == flipView) step = -step;
5371   if(step > 0) selectFlag = 1;
5372   if(!selectFlag) Sweep(step);
5373   return FALSE;
5374 }
5375
5376 void
5377 NextPiece (int step)
5378 {
5379     ChessSquare piece = boards[currentMove][toY][toX];
5380     do {
5381         pieceSweep -= step;
5382         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5383         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5384         if(!step) step = -1;
5385     } while(PieceToChar(pieceSweep) == '.');
5386     boards[currentMove][toY][toX] = pieceSweep;
5387     DrawPosition(FALSE, boards[currentMove]);
5388     boards[currentMove][toY][toX] = piece;
5389 }
5390 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5391 void
5392 AlphaRank (char *move, int n)
5393 {
5394 //    char *p = move, c; int x, y;
5395
5396     if (appData.debugMode) {
5397         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5398     }
5399
5400     if(move[1]=='*' &&
5401        move[2]>='0' && move[2]<='9' &&
5402        move[3]>='a' && move[3]<='x'    ) {
5403         move[1] = '@';
5404         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5405         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5406     } else
5407     if(move[0]>='0' && move[0]<='9' &&
5408        move[1]>='a' && move[1]<='x' &&
5409        move[2]>='0' && move[2]<='9' &&
5410        move[3]>='a' && move[3]<='x'    ) {
5411         /* input move, Shogi -> normal */
5412         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5413         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5414         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5415         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5416     } else
5417     if(move[1]=='@' &&
5418        move[3]>='0' && move[3]<='9' &&
5419        move[2]>='a' && move[2]<='x'    ) {
5420         move[1] = '*';
5421         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5422         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5423     } else
5424     if(
5425        move[0]>='a' && move[0]<='x' &&
5426        move[3]>='0' && move[3]<='9' &&
5427        move[2]>='a' && move[2]<='x'    ) {
5428          /* output move, normal -> Shogi */
5429         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5430         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5431         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5432         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5433         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5434     }
5435     if (appData.debugMode) {
5436         fprintf(debugFP, "   out = '%s'\n", move);
5437     }
5438 }
5439
5440 char yy_textstr[8000];
5441
5442 /* Parser for moves from gnuchess, ICS, or user typein box */
5443 Boolean
5444 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5445 {
5446     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5447
5448     switch (*moveType) {
5449       case WhitePromotion:
5450       case BlackPromotion:
5451       case WhiteNonPromotion:
5452       case BlackNonPromotion:
5453       case NormalMove:
5454       case FirstLeg:
5455       case WhiteCapturesEnPassant:
5456       case BlackCapturesEnPassant:
5457       case WhiteKingSideCastle:
5458       case WhiteQueenSideCastle:
5459       case BlackKingSideCastle:
5460       case BlackQueenSideCastle:
5461       case WhiteKingSideCastleWild:
5462       case WhiteQueenSideCastleWild:
5463       case BlackKingSideCastleWild:
5464       case BlackQueenSideCastleWild:
5465       /* Code added by Tord: */
5466       case WhiteHSideCastleFR:
5467       case WhiteASideCastleFR:
5468       case BlackHSideCastleFR:
5469       case BlackASideCastleFR:
5470       /* End of code added by Tord */
5471       case IllegalMove:         /* bug or odd chess variant */
5472         *fromX = currentMoveString[0] - AAA;
5473         *fromY = currentMoveString[1] - ONE;
5474         *toX = currentMoveString[2] - AAA;
5475         *toY = currentMoveString[3] - ONE;
5476         *promoChar = currentMoveString[4];
5477         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5478             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5479     if (appData.debugMode) {
5480         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5481     }
5482             *fromX = *fromY = *toX = *toY = 0;
5483             return FALSE;
5484         }
5485         if (appData.testLegality) {
5486           return (*moveType != IllegalMove);
5487         } else {
5488           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5489                          // [HGM] lion: if this is a double move we are less critical
5490                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5491         }
5492
5493       case WhiteDrop:
5494       case BlackDrop:
5495         *fromX = *moveType == WhiteDrop ?
5496           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5497           (int) CharToPiece(ToLower(currentMoveString[0]));
5498         *fromY = DROP_RANK;
5499         *toX = currentMoveString[2] - AAA;
5500         *toY = currentMoveString[3] - ONE;
5501         *promoChar = NULLCHAR;
5502         return TRUE;
5503
5504       case AmbiguousMove:
5505       case ImpossibleMove:
5506       case EndOfFile:
5507       case ElapsedTime:
5508       case Comment:
5509       case PGNTag:
5510       case NAG:
5511       case WhiteWins:
5512       case BlackWins:
5513       case GameIsDrawn:
5514       default:
5515     if (appData.debugMode) {
5516         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5517     }
5518         /* bug? */
5519         *fromX = *fromY = *toX = *toY = 0;
5520         *promoChar = NULLCHAR;
5521         return FALSE;
5522     }
5523 }
5524
5525 Boolean pushed = FALSE;
5526 char *lastParseAttempt;
5527
5528 void
5529 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5530 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5531   int fromX, fromY, toX, toY; char promoChar;
5532   ChessMove moveType;
5533   Boolean valid;
5534   int nr = 0;
5535
5536   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5537   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5538     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5539     pushed = TRUE;
5540   }
5541   endPV = forwardMostMove;
5542   do {
5543     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5544     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5545     lastParseAttempt = pv;
5546     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5547     if(!valid && nr == 0 &&
5548        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5549         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5550         // Hande case where played move is different from leading PV move
5551         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5552         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5553         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5554         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5555           endPV += 2; // if position different, keep this
5556           moveList[endPV-1][0] = fromX + AAA;
5557           moveList[endPV-1][1] = fromY + ONE;
5558           moveList[endPV-1][2] = toX + AAA;
5559           moveList[endPV-1][3] = toY + ONE;
5560           parseList[endPV-1][0] = NULLCHAR;
5561           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5562         }
5563       }
5564     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5565     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5566     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5567     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5568         valid++; // allow comments in PV
5569         continue;
5570     }
5571     nr++;
5572     if(endPV+1 > framePtr) break; // no space, truncate
5573     if(!valid) break;
5574     endPV++;
5575     CopyBoard(boards[endPV], boards[endPV-1]);
5576     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5577     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5578     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5579     CoordsToAlgebraic(boards[endPV - 1],
5580                              PosFlags(endPV - 1),
5581                              fromY, fromX, toY, toX, promoChar,
5582                              parseList[endPV - 1]);
5583   } while(valid);
5584   if(atEnd == 2) return; // used hidden, for PV conversion
5585   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5586   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5587   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5588                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5589   DrawPosition(TRUE, boards[currentMove]);
5590 }
5591
5592 int
5593 MultiPV (ChessProgramState *cps)
5594 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5595         int i;
5596         for(i=0; i<cps->nrOptions; i++)
5597             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5598                 return i;
5599         return -1;
5600 }
5601
5602 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5603
5604 Boolean
5605 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5606 {
5607         int startPV, multi, lineStart, origIndex = index;
5608         char *p, buf2[MSG_SIZ];
5609         ChessProgramState *cps = (pane ? &second : &first);
5610
5611         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5612         lastX = x; lastY = y;
5613         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5614         lineStart = startPV = index;
5615         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5616         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5617         index = startPV;
5618         do{ while(buf[index] && buf[index] != '\n') index++;
5619         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5620         buf[index] = 0;
5621         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5622                 int n = cps->option[multi].value;
5623                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5624                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5625                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5626                 cps->option[multi].value = n;
5627                 *start = *end = 0;
5628                 return FALSE;
5629         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5630                 ExcludeClick(origIndex - lineStart);
5631                 return FALSE;
5632         }
5633         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5634         *start = startPV; *end = index-1;
5635         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5636         return TRUE;
5637 }
5638
5639 char *
5640 PvToSAN (char *pv)
5641 {
5642         static char buf[10*MSG_SIZ];
5643         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5644         *buf = NULLCHAR;
5645         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5646         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5647         for(i = forwardMostMove; i<endPV; i++){
5648             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5649             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5650             k += strlen(buf+k);
5651         }
5652         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5653         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5654         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5655         endPV = savedEnd;
5656         return buf;
5657 }
5658
5659 Boolean
5660 LoadPV (int x, int y)
5661 { // called on right mouse click to load PV
5662   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5663   lastX = x; lastY = y;
5664   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5665   extendGame = FALSE;
5666   return TRUE;
5667 }
5668
5669 void
5670 UnLoadPV ()
5671 {
5672   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5673   if(endPV < 0) return;
5674   if(appData.autoCopyPV) CopyFENToClipboard();
5675   endPV = -1;
5676   if(extendGame && currentMove > forwardMostMove) {
5677         Boolean saveAnimate = appData.animate;
5678         if(pushed) {
5679             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5680                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5681             } else storedGames--; // abandon shelved tail of original game
5682         }
5683         pushed = FALSE;
5684         forwardMostMove = currentMove;
5685         currentMove = oldFMM;
5686         appData.animate = FALSE;
5687         ToNrEvent(forwardMostMove);
5688         appData.animate = saveAnimate;
5689   }
5690   currentMove = forwardMostMove;
5691   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5692   ClearPremoveHighlights();
5693   DrawPosition(TRUE, boards[currentMove]);
5694 }
5695
5696 void
5697 MovePV (int x, int y, int h)
5698 { // step through PV based on mouse coordinates (called on mouse move)
5699   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5700
5701   // we must somehow check if right button is still down (might be released off board!)
5702   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5703   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5704   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5705   if(!step) return;
5706   lastX = x; lastY = y;
5707
5708   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5709   if(endPV < 0) return;
5710   if(y < margin) step = 1; else
5711   if(y > h - margin) step = -1;
5712   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5713   currentMove += step;
5714   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5715   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5716                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5717   DrawPosition(FALSE, boards[currentMove]);
5718 }
5719
5720
5721 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5722 // All positions will have equal probability, but the current method will not provide a unique
5723 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5724 #define DARK 1
5725 #define LITE 2
5726 #define ANY 3
5727
5728 int squaresLeft[4];
5729 int piecesLeft[(int)BlackPawn];
5730 int seed, nrOfShuffles;
5731
5732 void
5733 GetPositionNumber ()
5734 {       // sets global variable seed
5735         int i;
5736
5737         seed = appData.defaultFrcPosition;
5738         if(seed < 0) { // randomize based on time for negative FRC position numbers
5739                 for(i=0; i<50; i++) seed += random();
5740                 seed = random() ^ random() >> 8 ^ random() << 8;
5741                 if(seed<0) seed = -seed;
5742         }
5743 }
5744
5745 int
5746 put (Board board, int pieceType, int rank, int n, int shade)
5747 // put the piece on the (n-1)-th empty squares of the given shade
5748 {
5749         int i;
5750
5751         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5752                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5753                         board[rank][i] = (ChessSquare) pieceType;
5754                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5755                         squaresLeft[ANY]--;
5756                         piecesLeft[pieceType]--;
5757                         return i;
5758                 }
5759         }
5760         return -1;
5761 }
5762
5763
5764 void
5765 AddOnePiece (Board board, int pieceType, int rank, int shade)
5766 // calculate where the next piece goes, (any empty square), and put it there
5767 {
5768         int i;
5769
5770         i = seed % squaresLeft[shade];
5771         nrOfShuffles *= squaresLeft[shade];
5772         seed /= squaresLeft[shade];
5773         put(board, pieceType, rank, i, shade);
5774 }
5775
5776 void
5777 AddTwoPieces (Board board, int pieceType, int rank)
5778 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5779 {
5780         int i, n=squaresLeft[ANY], j=n-1, k;
5781
5782         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5783         i = seed % k;  // pick one
5784         nrOfShuffles *= k;
5785         seed /= k;
5786         while(i >= j) i -= j--;
5787         j = n - 1 - j; i += j;
5788         put(board, pieceType, rank, j, ANY);
5789         put(board, pieceType, rank, i, ANY);
5790 }
5791
5792 void
5793 SetUpShuffle (Board board, int number)
5794 {
5795         int i, p, first=1;
5796
5797         GetPositionNumber(); nrOfShuffles = 1;
5798
5799         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5800         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5801         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5802
5803         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5804
5805         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5806             p = (int) board[0][i];
5807             if(p < (int) BlackPawn) piecesLeft[p] ++;
5808             board[0][i] = EmptySquare;
5809         }
5810
5811         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5812             // shuffles restricted to allow normal castling put KRR first
5813             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5814                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5815             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5816                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5817             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5818                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5819             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5820                 put(board, WhiteRook, 0, 0, ANY);
5821             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5822         }
5823
5824         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5825             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5826             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5827                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5828                 while(piecesLeft[p] >= 2) {
5829                     AddOnePiece(board, p, 0, LITE);
5830                     AddOnePiece(board, p, 0, DARK);
5831                 }
5832                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5833             }
5834
5835         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5836             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5837             // but we leave King and Rooks for last, to possibly obey FRC restriction
5838             if(p == (int)WhiteRook) continue;
5839             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5840             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5841         }
5842
5843         // now everything is placed, except perhaps King (Unicorn) and Rooks
5844
5845         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5846             // Last King gets castling rights
5847             while(piecesLeft[(int)WhiteUnicorn]) {
5848                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5849                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5850             }
5851
5852             while(piecesLeft[(int)WhiteKing]) {
5853                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5854                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5855             }
5856
5857
5858         } else {
5859             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5860             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5861         }
5862
5863         // Only Rooks can be left; simply place them all
5864         while(piecesLeft[(int)WhiteRook]) {
5865                 i = put(board, WhiteRook, 0, 0, ANY);
5866                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5867                         if(first) {
5868                                 first=0;
5869                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5870                         }
5871                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5872                 }
5873         }
5874         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5875             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5876         }
5877
5878         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5879 }
5880
5881 int
5882 SetCharTable (char *table, const char * map)
5883 /* [HGM] moved here from winboard.c because of its general usefulness */
5884 /*       Basically a safe strcpy that uses the last character as King */
5885 {
5886     int result = FALSE; int NrPieces;
5887
5888     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5889                     && NrPieces >= 12 && !(NrPieces&1)) {
5890         int i; /* [HGM] Accept even length from 12 to 34 */
5891
5892         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5893         for( i=0; i<NrPieces/2-1; i++ ) {
5894             table[i] = map[i];
5895             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5896         }
5897         table[(int) WhiteKing]  = map[NrPieces/2-1];
5898         table[(int) BlackKing]  = map[NrPieces-1];
5899
5900         result = TRUE;
5901     }
5902
5903     return result;
5904 }
5905
5906 void
5907 Prelude (Board board)
5908 {       // [HGM] superchess: random selection of exo-pieces
5909         int i, j, k; ChessSquare p;
5910         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5911
5912         GetPositionNumber(); // use FRC position number
5913
5914         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5915             SetCharTable(pieceToChar, appData.pieceToCharTable);
5916             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5917                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5918         }
5919
5920         j = seed%4;                 seed /= 4;
5921         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5922         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5923         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5924         j = seed%3 + (seed%3 >= j); seed /= 3;
5925         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5926         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5927         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5928         j = seed%3;                 seed /= 3;
5929         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5930         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5931         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5932         j = seed%2 + (seed%2 >= j); seed /= 2;
5933         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5934         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5935         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5936         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5937         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5938         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5939         put(board, exoPieces[0],    0, 0, ANY);
5940         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5941 }
5942
5943 void
5944 InitPosition (int redraw)
5945 {
5946     ChessSquare (* pieces)[BOARD_FILES];
5947     int i, j, pawnRow=1, pieceRows=1, overrule,
5948     oldx = gameInfo.boardWidth,
5949     oldy = gameInfo.boardHeight,
5950     oldh = gameInfo.holdingsWidth;
5951     static int oldv;
5952
5953     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5954
5955     /* [AS] Initialize pv info list [HGM] and game status */
5956     {
5957         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5958             pvInfoList[i].depth = 0;
5959             boards[i][EP_STATUS] = EP_NONE;
5960             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5961         }
5962
5963         initialRulePlies = 0; /* 50-move counter start */
5964
5965         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5966         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5967     }
5968
5969
5970     /* [HGM] logic here is completely changed. In stead of full positions */
5971     /* the initialized data only consist of the two backranks. The switch */
5972     /* selects which one we will use, which is than copied to the Board   */
5973     /* initialPosition, which for the rest is initialized by Pawns and    */
5974     /* empty squares. This initial position is then copied to boards[0],  */
5975     /* possibly after shuffling, so that it remains available.            */
5976
5977     gameInfo.holdingsWidth = 0; /* default board sizes */
5978     gameInfo.boardWidth    = 8;
5979     gameInfo.boardHeight   = 8;
5980     gameInfo.holdingsSize  = 0;
5981     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5982     for(i=0; i<BOARD_FILES-2; i++)
5983       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5984     initialPosition[EP_STATUS] = EP_NONE;
5985     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5986     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5987          SetCharTable(pieceNickName, appData.pieceNickNames);
5988     else SetCharTable(pieceNickName, "............");
5989     pieces = FIDEArray;
5990
5991     switch (gameInfo.variant) {
5992     case VariantFischeRandom:
5993       shuffleOpenings = TRUE;
5994     default:
5995       break;
5996     case VariantShatranj:
5997       pieces = ShatranjArray;
5998       nrCastlingRights = 0;
5999       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6000       break;
6001     case VariantMakruk:
6002       pieces = makrukArray;
6003       nrCastlingRights = 0;
6004       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6005       break;
6006     case VariantASEAN:
6007       pieces = aseanArray;
6008       nrCastlingRights = 0;
6009       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6010       break;
6011     case VariantTwoKings:
6012       pieces = twoKingsArray;
6013       break;
6014     case VariantGrand:
6015       pieces = GrandArray;
6016       nrCastlingRights = 0;
6017       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6018       gameInfo.boardWidth = 10;
6019       gameInfo.boardHeight = 10;
6020       gameInfo.holdingsSize = 7;
6021       break;
6022     case VariantCapaRandom:
6023       shuffleOpenings = TRUE;
6024     case VariantCapablanca:
6025       pieces = CapablancaArray;
6026       gameInfo.boardWidth = 10;
6027       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6028       break;
6029     case VariantGothic:
6030       pieces = GothicArray;
6031       gameInfo.boardWidth = 10;
6032       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6033       break;
6034     case VariantSChess:
6035       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6036       gameInfo.holdingsSize = 7;
6037       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6038       break;
6039     case VariantJanus:
6040       pieces = JanusArray;
6041       gameInfo.boardWidth = 10;
6042       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6043       nrCastlingRights = 6;
6044         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6045         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6046         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6047         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6048         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6049         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6050       break;
6051     case VariantFalcon:
6052       pieces = FalconArray;
6053       gameInfo.boardWidth = 10;
6054       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6055       break;
6056     case VariantXiangqi:
6057       pieces = XiangqiArray;
6058       gameInfo.boardWidth  = 9;
6059       gameInfo.boardHeight = 10;
6060       nrCastlingRights = 0;
6061       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6062       break;
6063     case VariantShogi:
6064       pieces = ShogiArray;
6065       gameInfo.boardWidth  = 9;
6066       gameInfo.boardHeight = 9;
6067       gameInfo.holdingsSize = 7;
6068       nrCastlingRights = 0;
6069       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6070       break;
6071     case VariantChu:
6072       pieces = ChuArray; pieceRows = 3;
6073       gameInfo.boardWidth  = 12;
6074       gameInfo.boardHeight = 12;
6075       nrCastlingRights = 0;
6076       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6077                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6078       break;
6079     case VariantCourier:
6080       pieces = CourierArray;
6081       gameInfo.boardWidth  = 12;
6082       nrCastlingRights = 0;
6083       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6084       break;
6085     case VariantKnightmate:
6086       pieces = KnightmateArray;
6087       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6088       break;
6089     case VariantSpartan:
6090       pieces = SpartanArray;
6091       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6092       break;
6093     case VariantLion:
6094       pieces = lionArray;
6095       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6096       break;
6097     case VariantChuChess:
6098       pieces = ChuChessArray;
6099       gameInfo.boardWidth = 10;
6100       gameInfo.boardHeight = 10;
6101       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6102       break;
6103     case VariantFairy:
6104       pieces = fairyArray;
6105       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6106       break;
6107     case VariantGreat:
6108       pieces = GreatArray;
6109       gameInfo.boardWidth = 10;
6110       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6111       gameInfo.holdingsSize = 8;
6112       break;
6113     case VariantSuper:
6114       pieces = FIDEArray;
6115       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6116       gameInfo.holdingsSize = 8;
6117       startedFromSetupPosition = TRUE;
6118       break;
6119     case VariantCrazyhouse:
6120     case VariantBughouse:
6121       pieces = FIDEArray;
6122       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6123       gameInfo.holdingsSize = 5;
6124       break;
6125     case VariantWildCastle:
6126       pieces = FIDEArray;
6127       /* !!?shuffle with kings guaranteed to be on d or e file */
6128       shuffleOpenings = 1;
6129       break;
6130     case VariantNoCastle:
6131       pieces = FIDEArray;
6132       nrCastlingRights = 0;
6133       /* !!?unconstrained back-rank shuffle */
6134       shuffleOpenings = 1;
6135       break;
6136     }
6137
6138     overrule = 0;
6139     if(appData.NrFiles >= 0) {
6140         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6141         gameInfo.boardWidth = appData.NrFiles;
6142     }
6143     if(appData.NrRanks >= 0) {
6144         gameInfo.boardHeight = appData.NrRanks;
6145     }
6146     if(appData.holdingsSize >= 0) {
6147         i = appData.holdingsSize;
6148         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6149         gameInfo.holdingsSize = i;
6150     }
6151     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6152     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6153         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6154
6155     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6156     if(pawnRow < 1) pawnRow = 1;
6157     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6158        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6159     if(gameInfo.variant == VariantChu) pawnRow = 3;
6160
6161     /* User pieceToChar list overrules defaults */
6162     if(appData.pieceToCharTable != NULL)
6163         SetCharTable(pieceToChar, appData.pieceToCharTable);
6164
6165     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6166
6167         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6168             s = (ChessSquare) 0; /* account holding counts in guard band */
6169         for( i=0; i<BOARD_HEIGHT; i++ )
6170             initialPosition[i][j] = s;
6171
6172         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6173         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6174         initialPosition[pawnRow][j] = WhitePawn;
6175         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6176         if(gameInfo.variant == VariantXiangqi) {
6177             if(j&1) {
6178                 initialPosition[pawnRow][j] =
6179                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6180                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6181                    initialPosition[2][j] = WhiteCannon;
6182                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6183                 }
6184             }
6185         }
6186         if(gameInfo.variant == VariantChu) {
6187              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6188                initialPosition[pawnRow+1][j] = WhiteCobra,
6189                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6190              for(i=1; i<pieceRows; i++) {
6191                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6192                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6193              }
6194         }
6195         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6196             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6197                initialPosition[0][j] = WhiteRook;
6198                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6199             }
6200         }
6201         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6202     }
6203     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6204     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6205
6206             j=BOARD_LEFT+1;
6207             initialPosition[1][j] = WhiteBishop;
6208             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6209             j=BOARD_RGHT-2;
6210             initialPosition[1][j] = WhiteRook;
6211             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6212     }
6213
6214     if( nrCastlingRights == -1) {
6215         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6216         /*       This sets default castling rights from none to normal corners   */
6217         /* Variants with other castling rights must set them themselves above    */
6218         nrCastlingRights = 6;
6219
6220         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6221         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6222         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6223         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6224         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6225         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6226      }
6227
6228      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6229      if(gameInfo.variant == VariantGreat) { // promotion commoners
6230         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6231         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6232         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6233         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6234      }
6235      if( gameInfo.variant == VariantSChess ) {
6236       initialPosition[1][0] = BlackMarshall;
6237       initialPosition[2][0] = BlackAngel;
6238       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6239       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6240       initialPosition[1][1] = initialPosition[2][1] =
6241       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6242      }
6243   if (appData.debugMode) {
6244     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6245   }
6246     if(shuffleOpenings) {
6247         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6248         startedFromSetupPosition = TRUE;
6249     }
6250     if(startedFromPositionFile) {
6251       /* [HGM] loadPos: use PositionFile for every new game */
6252       CopyBoard(initialPosition, filePosition);
6253       for(i=0; i<nrCastlingRights; i++)
6254           initialRights[i] = filePosition[CASTLING][i];
6255       startedFromSetupPosition = TRUE;
6256     }
6257
6258     CopyBoard(boards[0], initialPosition);
6259
6260     if(oldx != gameInfo.boardWidth ||
6261        oldy != gameInfo.boardHeight ||
6262        oldv != gameInfo.variant ||
6263        oldh != gameInfo.holdingsWidth
6264                                          )
6265             InitDrawingSizes(-2 ,0);
6266
6267     oldv = gameInfo.variant;
6268     if (redraw)
6269       DrawPosition(TRUE, boards[currentMove]);
6270 }
6271
6272 void
6273 SendBoard (ChessProgramState *cps, int moveNum)
6274 {
6275     char message[MSG_SIZ];
6276
6277     if (cps->useSetboard) {
6278       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6279       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6280       SendToProgram(message, cps);
6281       free(fen);
6282
6283     } else {
6284       ChessSquare *bp;
6285       int i, j, left=0, right=BOARD_WIDTH;
6286       /* Kludge to set black to move, avoiding the troublesome and now
6287        * deprecated "black" command.
6288        */
6289       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6290         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6291
6292       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6293
6294       SendToProgram("edit\n", cps);
6295       SendToProgram("#\n", cps);
6296       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6297         bp = &boards[moveNum][i][left];
6298         for (j = left; j < right; j++, bp++) {
6299           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6300           if ((int) *bp < (int) BlackPawn) {
6301             if(j == BOARD_RGHT+1)
6302                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6303             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6304             if(message[0] == '+' || message[0] == '~') {
6305               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6306                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6307                         AAA + j, ONE + i);
6308             }
6309             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6310                 message[1] = BOARD_RGHT   - 1 - j + '1';
6311                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6312             }
6313             SendToProgram(message, cps);
6314           }
6315         }
6316       }
6317
6318       SendToProgram("c\n", cps);
6319       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6320         bp = &boards[moveNum][i][left];
6321         for (j = left; j < right; j++, bp++) {
6322           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6323           if (((int) *bp != (int) EmptySquare)
6324               && ((int) *bp >= (int) BlackPawn)) {
6325             if(j == BOARD_LEFT-2)
6326                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6327             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6328                     AAA + j, ONE + i);
6329             if(message[0] == '+' || message[0] == '~') {
6330               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6331                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6332                         AAA + j, ONE + i);
6333             }
6334             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6335                 message[1] = BOARD_RGHT   - 1 - j + '1';
6336                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6337             }
6338             SendToProgram(message, cps);
6339           }
6340         }
6341       }
6342
6343       SendToProgram(".\n", cps);
6344     }
6345     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6346 }
6347
6348 char exclusionHeader[MSG_SIZ];
6349 int exCnt, excludePtr;
6350 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6351 static Exclusion excluTab[200];
6352 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6353
6354 static void
6355 WriteMap (int s)
6356 {
6357     int j;
6358     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6359     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6360 }
6361
6362 static void
6363 ClearMap ()
6364 {
6365     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6366     excludePtr = 24; exCnt = 0;
6367     WriteMap(0);
6368 }
6369
6370 static void
6371 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6372 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6373     char buf[2*MOVE_LEN], *p;
6374     Exclusion *e = excluTab;
6375     int i;
6376     for(i=0; i<exCnt; i++)
6377         if(e[i].ff == fromX && e[i].fr == fromY &&
6378            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6379     if(i == exCnt) { // was not in exclude list; add it
6380         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6381         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6382             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6383             return; // abort
6384         }
6385         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6386         excludePtr++; e[i].mark = excludePtr++;
6387         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6388         exCnt++;
6389     }
6390     exclusionHeader[e[i].mark] = state;
6391 }
6392
6393 static int
6394 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6395 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6396     char buf[MSG_SIZ];
6397     int j, k;
6398     ChessMove moveType;
6399     if((signed char)promoChar == -1) { // kludge to indicate best move
6400         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6401             return 1; // if unparsable, abort
6402     }
6403     // update exclusion map (resolving toggle by consulting existing state)
6404     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6405     j = k%8; k >>= 3;
6406     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6407     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6408          excludeMap[k] |=   1<<j;
6409     else excludeMap[k] &= ~(1<<j);
6410     // update header
6411     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6412     // inform engine
6413     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6414     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6415     SendToBoth(buf);
6416     return (state == '+');
6417 }
6418
6419 static void
6420 ExcludeClick (int index)
6421 {
6422     int i, j;
6423     Exclusion *e = excluTab;
6424     if(index < 25) { // none, best or tail clicked
6425         if(index < 13) { // none: include all
6426             WriteMap(0); // clear map
6427             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6428             SendToBoth("include all\n"); // and inform engine
6429         } else if(index > 18) { // tail
6430             if(exclusionHeader[19] == '-') { // tail was excluded
6431                 SendToBoth("include all\n");
6432                 WriteMap(0); // clear map completely
6433                 // now re-exclude selected moves
6434                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6435                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6436             } else { // tail was included or in mixed state
6437                 SendToBoth("exclude all\n");
6438                 WriteMap(0xFF); // fill map completely
6439                 // now re-include selected moves
6440                 j = 0; // count them
6441                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6442                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6443                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6444             }
6445         } else { // best
6446             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6447         }
6448     } else {
6449         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6450             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6451             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6452             break;
6453         }
6454     }
6455 }
6456
6457 ChessSquare
6458 DefaultPromoChoice (int white)
6459 {
6460     ChessSquare result;
6461     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6462        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6463         result = WhiteFerz; // no choice
6464     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6465         result= WhiteKing; // in Suicide Q is the last thing we want
6466     else if(gameInfo.variant == VariantSpartan)
6467         result = white ? WhiteQueen : WhiteAngel;
6468     else result = WhiteQueen;
6469     if(!white) result = WHITE_TO_BLACK result;
6470     return result;
6471 }
6472
6473 static int autoQueen; // [HGM] oneclick
6474
6475 int
6476 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6477 {
6478     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6479     /* [HGM] add Shogi promotions */
6480     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6481     ChessSquare piece, partner;
6482     ChessMove moveType;
6483     Boolean premove;
6484
6485     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6486     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6487
6488     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6489       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6490         return FALSE;
6491
6492     piece = boards[currentMove][fromY][fromX];
6493     if(gameInfo.variant == VariantChu) {
6494         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6495         promotionZoneSize = BOARD_HEIGHT/3;
6496         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6497     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6498         promotionZoneSize = BOARD_HEIGHT/3;
6499         highestPromotingPiece = (int)WhiteAlfil;
6500     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6501         promotionZoneSize = 3;
6502     }
6503
6504     // Treat Lance as Pawn when it is not representing Amazon
6505     if(gameInfo.variant != VariantSuper) {
6506         if(piece == WhiteLance) piece = WhitePawn; else
6507         if(piece == BlackLance) piece = BlackPawn;
6508     }
6509
6510     // next weed out all moves that do not touch the promotion zone at all
6511     if((int)piece >= BlackPawn) {
6512         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6513              return FALSE;
6514         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6515         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6516     } else {
6517         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6518            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6519         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6520              return FALSE;
6521     }
6522
6523     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6524
6525     // weed out mandatory Shogi promotions
6526     if(gameInfo.variant == VariantShogi) {
6527         if(piece >= BlackPawn) {
6528             if(toY == 0 && piece == BlackPawn ||
6529                toY == 0 && piece == BlackQueen ||
6530                toY <= 1 && piece == BlackKnight) {
6531                 *promoChoice = '+';
6532                 return FALSE;
6533             }
6534         } else {
6535             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6536                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6537                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6538                 *promoChoice = '+';
6539                 return FALSE;
6540             }
6541         }
6542     }
6543
6544     // weed out obviously illegal Pawn moves
6545     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6546         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6547         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6548         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6549         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6550         // note we are not allowed to test for valid (non-)capture, due to premove
6551     }
6552
6553     // we either have a choice what to promote to, or (in Shogi) whether to promote
6554     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6555        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6556         ChessSquare p=BlackFerz;  // no choice
6557         while(p < EmptySquare) {  //but make sure we use piece that exists
6558             *promoChoice = PieceToChar(p++);
6559             if(*promoChoice != '.') break;
6560         }
6561         return FALSE;
6562     }
6563     // no sense asking what we must promote to if it is going to explode...
6564     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6565         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6566         return FALSE;
6567     }
6568     // give caller the default choice even if we will not make it
6569     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6570     partner = piece; // pieces can promote if the pieceToCharTable says so
6571     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6572     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6573     if(        sweepSelect && gameInfo.variant != VariantGreat
6574                            && gameInfo.variant != VariantGrand
6575                            && gameInfo.variant != VariantSuper) return FALSE;
6576     if(autoQueen) return FALSE; // predetermined
6577
6578     // suppress promotion popup on illegal moves that are not premoves
6579     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6580               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6581     if(appData.testLegality && !premove) {
6582         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6583                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6584         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6585         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6586             return FALSE;
6587     }
6588
6589     return TRUE;
6590 }
6591
6592 int
6593 InPalace (int row, int column)
6594 {   /* [HGM] for Xiangqi */
6595     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6596          column < (BOARD_WIDTH + 4)/2 &&
6597          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6598     return FALSE;
6599 }
6600
6601 int
6602 PieceForSquare (int x, int y)
6603 {
6604   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6605      return -1;
6606   else
6607      return boards[currentMove][y][x];
6608 }
6609
6610 int
6611 OKToStartUserMove (int x, int y)
6612 {
6613     ChessSquare from_piece;
6614     int white_piece;
6615
6616     if (matchMode) return FALSE;
6617     if (gameMode == EditPosition) return TRUE;
6618
6619     if (x >= 0 && y >= 0)
6620       from_piece = boards[currentMove][y][x];
6621     else
6622       from_piece = EmptySquare;
6623
6624     if (from_piece == EmptySquare) return FALSE;
6625
6626     white_piece = (int)from_piece >= (int)WhitePawn &&
6627       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6628
6629     switch (gameMode) {
6630       case AnalyzeFile:
6631       case TwoMachinesPlay:
6632       case EndOfGame:
6633         return FALSE;
6634
6635       case IcsObserving:
6636       case IcsIdle:
6637         return FALSE;
6638
6639       case MachinePlaysWhite:
6640       case IcsPlayingBlack:
6641         if (appData.zippyPlay) return FALSE;
6642         if (white_piece) {
6643             DisplayMoveError(_("You are playing Black"));
6644             return FALSE;
6645         }
6646         break;
6647
6648       case MachinePlaysBlack:
6649       case IcsPlayingWhite:
6650         if (appData.zippyPlay) return FALSE;
6651         if (!white_piece) {
6652             DisplayMoveError(_("You are playing White"));
6653             return FALSE;
6654         }
6655         break;
6656
6657       case PlayFromGameFile:
6658             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6659       case EditGame:
6660         if (!white_piece && WhiteOnMove(currentMove)) {
6661             DisplayMoveError(_("It is White's turn"));
6662             return FALSE;
6663         }
6664         if (white_piece && !WhiteOnMove(currentMove)) {
6665             DisplayMoveError(_("It is Black's turn"));
6666             return FALSE;
6667         }
6668         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6669             /* Editing correspondence game history */
6670             /* Could disallow this or prompt for confirmation */
6671             cmailOldMove = -1;
6672         }
6673         break;
6674
6675       case BeginningOfGame:
6676         if (appData.icsActive) return FALSE;
6677         if (!appData.noChessProgram) {
6678             if (!white_piece) {
6679                 DisplayMoveError(_("You are playing White"));
6680                 return FALSE;
6681             }
6682         }
6683         break;
6684
6685       case Training:
6686         if (!white_piece && WhiteOnMove(currentMove)) {
6687             DisplayMoveError(_("It is White's turn"));
6688             return FALSE;
6689         }
6690         if (white_piece && !WhiteOnMove(currentMove)) {
6691             DisplayMoveError(_("It is Black's turn"));
6692             return FALSE;
6693         }
6694         break;
6695
6696       default:
6697       case IcsExamining:
6698         break;
6699     }
6700     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6701         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6702         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6703         && gameMode != AnalyzeFile && gameMode != Training) {
6704         DisplayMoveError(_("Displayed position is not current"));
6705         return FALSE;
6706     }
6707     return TRUE;
6708 }
6709
6710 Boolean
6711 OnlyMove (int *x, int *y, Boolean captures)
6712 {
6713     DisambiguateClosure cl;
6714     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6715     switch(gameMode) {
6716       case MachinePlaysBlack:
6717       case IcsPlayingWhite:
6718       case BeginningOfGame:
6719         if(!WhiteOnMove(currentMove)) return FALSE;
6720         break;
6721       case MachinePlaysWhite:
6722       case IcsPlayingBlack:
6723         if(WhiteOnMove(currentMove)) return FALSE;
6724         break;
6725       case EditGame:
6726         break;
6727       default:
6728         return FALSE;
6729     }
6730     cl.pieceIn = EmptySquare;
6731     cl.rfIn = *y;
6732     cl.ffIn = *x;
6733     cl.rtIn = -1;
6734     cl.ftIn = -1;
6735     cl.promoCharIn = NULLCHAR;
6736     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6737     if( cl.kind == NormalMove ||
6738         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6739         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6740         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6741       fromX = cl.ff;
6742       fromY = cl.rf;
6743       *x = cl.ft;
6744       *y = cl.rt;
6745       return TRUE;
6746     }
6747     if(cl.kind != ImpossibleMove) return FALSE;
6748     cl.pieceIn = EmptySquare;
6749     cl.rfIn = -1;
6750     cl.ffIn = -1;
6751     cl.rtIn = *y;
6752     cl.ftIn = *x;
6753     cl.promoCharIn = NULLCHAR;
6754     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6755     if( cl.kind == NormalMove ||
6756         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6757         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6758         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6759       fromX = cl.ff;
6760       fromY = cl.rf;
6761       *x = cl.ft;
6762       *y = cl.rt;
6763       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6764       return TRUE;
6765     }
6766     return FALSE;
6767 }
6768
6769 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6770 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6771 int lastLoadGameUseList = FALSE;
6772 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6773 ChessMove lastLoadGameStart = EndOfFile;
6774 int doubleClick;
6775
6776 void
6777 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6778 {
6779     ChessMove moveType;
6780     ChessSquare pup;
6781     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6782
6783     /* Check if the user is playing in turn.  This is complicated because we
6784        let the user "pick up" a piece before it is his turn.  So the piece he
6785        tried to pick up may have been captured by the time he puts it down!
6786        Therefore we use the color the user is supposed to be playing in this
6787        test, not the color of the piece that is currently on the starting
6788        square---except in EditGame mode, where the user is playing both
6789        sides; fortunately there the capture race can't happen.  (It can
6790        now happen in IcsExamining mode, but that's just too bad.  The user
6791        will get a somewhat confusing message in that case.)
6792        */
6793
6794     switch (gameMode) {
6795       case AnalyzeFile:
6796       case TwoMachinesPlay:
6797       case EndOfGame:
6798       case IcsObserving:
6799       case IcsIdle:
6800         /* We switched into a game mode where moves are not accepted,
6801            perhaps while the mouse button was down. */
6802         return;
6803
6804       case MachinePlaysWhite:
6805         /* User is moving for Black */
6806         if (WhiteOnMove(currentMove)) {
6807             DisplayMoveError(_("It is White's turn"));
6808             return;
6809         }
6810         break;
6811
6812       case MachinePlaysBlack:
6813         /* User is moving for White */
6814         if (!WhiteOnMove(currentMove)) {
6815             DisplayMoveError(_("It is Black's turn"));
6816             return;
6817         }
6818         break;
6819
6820       case PlayFromGameFile:
6821             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6822       case EditGame:
6823       case IcsExamining:
6824       case BeginningOfGame:
6825       case AnalyzeMode:
6826       case Training:
6827         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6828         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6829             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6830             /* User is moving for Black */
6831             if (WhiteOnMove(currentMove)) {
6832                 DisplayMoveError(_("It is White's turn"));
6833                 return;
6834             }
6835         } else {
6836             /* User is moving for White */
6837             if (!WhiteOnMove(currentMove)) {
6838                 DisplayMoveError(_("It is Black's turn"));
6839                 return;
6840             }
6841         }
6842         break;
6843
6844       case IcsPlayingBlack:
6845         /* User is moving for Black */
6846         if (WhiteOnMove(currentMove)) {
6847             if (!appData.premove) {
6848                 DisplayMoveError(_("It is White's turn"));
6849             } else if (toX >= 0 && toY >= 0) {
6850                 premoveToX = toX;
6851                 premoveToY = toY;
6852                 premoveFromX = fromX;
6853                 premoveFromY = fromY;
6854                 premovePromoChar = promoChar;
6855                 gotPremove = 1;
6856                 if (appData.debugMode)
6857                     fprintf(debugFP, "Got premove: fromX %d,"
6858                             "fromY %d, toX %d, toY %d\n",
6859                             fromX, fromY, toX, toY);
6860             }
6861             return;
6862         }
6863         break;
6864
6865       case IcsPlayingWhite:
6866         /* User is moving for White */
6867         if (!WhiteOnMove(currentMove)) {
6868             if (!appData.premove) {
6869                 DisplayMoveError(_("It is Black's turn"));
6870             } else if (toX >= 0 && toY >= 0) {
6871                 premoveToX = toX;
6872                 premoveToY = toY;
6873                 premoveFromX = fromX;
6874                 premoveFromY = fromY;
6875                 premovePromoChar = promoChar;
6876                 gotPremove = 1;
6877                 if (appData.debugMode)
6878                     fprintf(debugFP, "Got premove: fromX %d,"
6879                             "fromY %d, toX %d, toY %d\n",
6880                             fromX, fromY, toX, toY);
6881             }
6882             return;
6883         }
6884         break;
6885
6886       default:
6887         break;
6888
6889       case EditPosition:
6890         /* EditPosition, empty square, or different color piece;
6891            click-click move is possible */
6892         if (toX == -2 || toY == -2) {
6893             boards[0][fromY][fromX] = EmptySquare;
6894             DrawPosition(FALSE, boards[currentMove]);
6895             return;
6896         } else if (toX >= 0 && toY >= 0) {
6897             boards[0][toY][toX] = boards[0][fromY][fromX];
6898             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6899                 if(boards[0][fromY][0] != EmptySquare) {
6900                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6901                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6902                 }
6903             } else
6904             if(fromX == BOARD_RGHT+1) {
6905                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6906                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6907                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6908                 }
6909             } else
6910             boards[0][fromY][fromX] = gatingPiece;
6911             DrawPosition(FALSE, boards[currentMove]);
6912             return;
6913         }
6914         return;
6915     }
6916
6917     if(toX < 0 || toY < 0) return;
6918     pup = boards[currentMove][toY][toX];
6919
6920     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6921     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6922          if( pup != EmptySquare ) return;
6923          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6924            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6925                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6926            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6927            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6928            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6929            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6930          fromY = DROP_RANK;
6931     }
6932
6933     /* [HGM] always test for legality, to get promotion info */
6934     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6935                                          fromY, fromX, toY, toX, promoChar);
6936
6937     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6938
6939     /* [HGM] but possibly ignore an IllegalMove result */
6940     if (appData.testLegality) {
6941         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6942             DisplayMoveError(_("Illegal move"));
6943             return;
6944         }
6945     }
6946
6947     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6948         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6949              ClearPremoveHighlights(); // was included
6950         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6951         return;
6952     }
6953
6954     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6955 }
6956
6957 /* Common tail of UserMoveEvent and DropMenuEvent */
6958 int
6959 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6960 {
6961     char *bookHit = 0;
6962
6963     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6964         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6965         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6966         if(WhiteOnMove(currentMove)) {
6967             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6968         } else {
6969             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6970         }
6971     }
6972
6973     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6974        move type in caller when we know the move is a legal promotion */
6975     if(moveType == NormalMove && promoChar)
6976         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6977
6978     /* [HGM] <popupFix> The following if has been moved here from
6979        UserMoveEvent(). Because it seemed to belong here (why not allow
6980        piece drops in training games?), and because it can only be
6981        performed after it is known to what we promote. */
6982     if (gameMode == Training) {
6983       /* compare the move played on the board to the next move in the
6984        * game. If they match, display the move and the opponent's response.
6985        * If they don't match, display an error message.
6986        */
6987       int saveAnimate;
6988       Board testBoard;
6989       CopyBoard(testBoard, boards[currentMove]);
6990       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6991
6992       if (CompareBoards(testBoard, boards[currentMove+1])) {
6993         ForwardInner(currentMove+1);
6994
6995         /* Autoplay the opponent's response.
6996          * if appData.animate was TRUE when Training mode was entered,
6997          * the response will be animated.
6998          */
6999         saveAnimate = appData.animate;
7000         appData.animate = animateTraining;
7001         ForwardInner(currentMove+1);
7002         appData.animate = saveAnimate;
7003
7004         /* check for the end of the game */
7005         if (currentMove >= forwardMostMove) {
7006           gameMode = PlayFromGameFile;
7007           ModeHighlight();
7008           SetTrainingModeOff();
7009           DisplayInformation(_("End of game"));
7010         }
7011       } else {
7012         DisplayError(_("Incorrect move"), 0);
7013       }
7014       return 1;
7015     }
7016
7017   /* Ok, now we know that the move is good, so we can kill
7018      the previous line in Analysis Mode */
7019   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7020                                 && currentMove < forwardMostMove) {
7021     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7022     else forwardMostMove = currentMove;
7023   }
7024
7025   ClearMap();
7026
7027   /* If we need the chess program but it's dead, restart it */
7028   ResurrectChessProgram();
7029
7030   /* A user move restarts a paused game*/
7031   if (pausing)
7032     PauseEvent();
7033
7034   thinkOutput[0] = NULLCHAR;
7035
7036   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7037
7038   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7039     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7040     return 1;
7041   }
7042
7043   if (gameMode == BeginningOfGame) {
7044     if (appData.noChessProgram) {
7045       gameMode = EditGame;
7046       SetGameInfo();
7047     } else {
7048       char buf[MSG_SIZ];
7049       gameMode = MachinePlaysBlack;
7050       StartClocks();
7051       SetGameInfo();
7052       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7053       DisplayTitle(buf);
7054       if (first.sendName) {
7055         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7056         SendToProgram(buf, &first);
7057       }
7058       StartClocks();
7059     }
7060     ModeHighlight();
7061   }
7062
7063   /* Relay move to ICS or chess engine */
7064   if (appData.icsActive) {
7065     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7066         gameMode == IcsExamining) {
7067       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7068         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7069         SendToICS("draw ");
7070         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7071       }
7072       // also send plain move, in case ICS does not understand atomic claims
7073       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7074       ics_user_moved = 1;
7075     }
7076   } else {
7077     if (first.sendTime && (gameMode == BeginningOfGame ||
7078                            gameMode == MachinePlaysWhite ||
7079                            gameMode == MachinePlaysBlack)) {
7080       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7081     }
7082     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7083          // [HGM] book: if program might be playing, let it use book
7084         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7085         first.maybeThinking = TRUE;
7086     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7087         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7088         SendBoard(&first, currentMove+1);
7089         if(second.analyzing) {
7090             if(!second.useSetboard) SendToProgram("undo\n", &second);
7091             SendBoard(&second, currentMove+1);
7092         }
7093     } else {
7094         SendMoveToProgram(forwardMostMove-1, &first);
7095         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7096     }
7097     if (currentMove == cmailOldMove + 1) {
7098       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7099     }
7100   }
7101
7102   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7103
7104   switch (gameMode) {
7105   case EditGame:
7106     if(appData.testLegality)
7107     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7108     case MT_NONE:
7109     case MT_CHECK:
7110       break;
7111     case MT_CHECKMATE:
7112     case MT_STAINMATE:
7113       if (WhiteOnMove(currentMove)) {
7114         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7115       } else {
7116         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7117       }
7118       break;
7119     case MT_STALEMATE:
7120       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7121       break;
7122     }
7123     break;
7124
7125   case MachinePlaysBlack:
7126   case MachinePlaysWhite:
7127     /* disable certain menu options while machine is thinking */
7128     SetMachineThinkingEnables();
7129     break;
7130
7131   default:
7132     break;
7133   }
7134
7135   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7136   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7137
7138   if(bookHit) { // [HGM] book: simulate book reply
7139         static char bookMove[MSG_SIZ]; // a bit generous?
7140
7141         programStats.nodes = programStats.depth = programStats.time =
7142         programStats.score = programStats.got_only_move = 0;
7143         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7144
7145         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7146         strcat(bookMove, bookHit);
7147         HandleMachineMove(bookMove, &first);
7148   }
7149   return 1;
7150 }
7151
7152 void
7153 MarkByFEN(char *fen)
7154 {
7155         int r, f;
7156         if(!appData.markers || !appData.highlightDragging) return;
7157         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7158         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7159         while(*fen) {
7160             int s = 0;
7161             marker[r][f] = 0;
7162             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7163             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7164             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7165             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7166             if(*fen == 'T') marker[r][f++] = 0; else
7167             if(*fen == 'Y') marker[r][f++] = 1; else
7168             if(*fen == 'G') marker[r][f++] = 3; else
7169             if(*fen == 'B') marker[r][f++] = 4; else
7170             if(*fen == 'C') marker[r][f++] = 5; else
7171             if(*fen == 'M') marker[r][f++] = 6; else
7172             if(*fen == 'W') marker[r][f++] = 7; else
7173             if(*fen == 'D') marker[r][f++] = 8; else
7174             if(*fen == 'R') marker[r][f++] = 2; else {
7175                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7176               f += s; fen -= s>0;
7177             }
7178             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7179             if(r < 0) break;
7180             fen++;
7181         }
7182         DrawPosition(TRUE, NULL);
7183 }
7184
7185 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7186
7187 void
7188 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7189 {
7190     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7191     Markers *m = (Markers *) closure;
7192     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7193         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7194                          || kind == WhiteCapturesEnPassant
7195                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7196     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7197 }
7198
7199 static int hoverSavedValid;
7200
7201 void
7202 MarkTargetSquares (int clear)
7203 {
7204   int x, y, sum=0;
7205   if(clear) { // no reason to ever suppress clearing
7206     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7207     hoverSavedValid = 0;
7208     if(!sum) return; // nothing was cleared,no redraw needed
7209   } else {
7210     int capt = 0;
7211     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7212        !appData.testLegality || gameMode == EditPosition) return;
7213     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7214     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7215       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7216       if(capt)
7217       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7218     }
7219   }
7220   DrawPosition(FALSE, NULL);
7221 }
7222
7223 int
7224 Explode (Board board, int fromX, int fromY, int toX, int toY)
7225 {
7226     if(gameInfo.variant == VariantAtomic &&
7227        (board[toY][toX] != EmptySquare ||                     // capture?
7228         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7229                          board[fromY][fromX] == BlackPawn   )
7230       )) {
7231         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7232         return TRUE;
7233     }
7234     return FALSE;
7235 }
7236
7237 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7238
7239 int
7240 CanPromote (ChessSquare piece, int y)
7241 {
7242         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7243         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7244         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7245         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7246            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7247            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7248          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7249         return (piece == BlackPawn && y <= zone ||
7250                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7251                 piece == BlackLance && y == 1 ||
7252                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7253 }
7254
7255 void
7256 HoverEvent (int xPix, int yPix, int x, int y)
7257 {
7258         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7259         int r, f;
7260         if(!first.highlight) return;
7261         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7262         if(x == oldX && y == oldY) return; // only do something if we enter new square
7263         oldFromX = fromX; oldFromY = fromY;
7264         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7265           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7266             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7267           hoverSavedValid = 1;
7268         } else if(oldX != x || oldY != y) {
7269           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7270           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7271           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7272             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7273           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7274             char buf[MSG_SIZ];
7275             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7276             SendToProgram(buf, &first);
7277           }
7278           oldX = x; oldY = y;
7279 //        SetHighlights(fromX, fromY, x, y);
7280         }
7281 }
7282
7283 void ReportClick(char *action, int x, int y)
7284 {
7285         char buf[MSG_SIZ]; // Inform engine of what user does
7286         int r, f;
7287         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7288           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7289         if(!first.highlight || gameMode == EditPosition) return;
7290         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7291         SendToProgram(buf, &first);
7292 }
7293
7294 void
7295 LeftClick (ClickType clickType, int xPix, int yPix)
7296 {
7297     int x, y;
7298     Boolean saveAnimate;
7299     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7300     char promoChoice = NULLCHAR;
7301     ChessSquare piece;
7302     static TimeMark lastClickTime, prevClickTime;
7303
7304     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7305
7306     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7307
7308     if (clickType == Press) ErrorPopDown();
7309     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7310
7311     x = EventToSquare(xPix, BOARD_WIDTH);
7312     y = EventToSquare(yPix, BOARD_HEIGHT);
7313     if (!flipView && y >= 0) {
7314         y = BOARD_HEIGHT - 1 - y;
7315     }
7316     if (flipView && x >= 0) {
7317         x = BOARD_WIDTH - 1 - x;
7318     }
7319
7320     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7321         defaultPromoChoice = promoSweep;
7322         promoSweep = EmptySquare;   // terminate sweep
7323         promoDefaultAltered = TRUE;
7324         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7325     }
7326
7327     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7328         if(clickType == Release) return; // ignore upclick of click-click destination
7329         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7330         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7331         if(gameInfo.holdingsWidth &&
7332                 (WhiteOnMove(currentMove)
7333                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7334                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7335             // click in right holdings, for determining promotion piece
7336             ChessSquare p = boards[currentMove][y][x];
7337             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7338             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7339             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7340                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7341                 fromX = fromY = -1;
7342                 return;
7343             }
7344         }
7345         DrawPosition(FALSE, boards[currentMove]);
7346         return;
7347     }
7348
7349     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7350     if(clickType == Press
7351             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7352               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7353               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7354         return;
7355
7356     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7357         // could be static click on premove from-square: abort premove
7358         gotPremove = 0;
7359         ClearPremoveHighlights();
7360     }
7361
7362     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7363         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7364
7365     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7366         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7367                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7368         defaultPromoChoice = DefaultPromoChoice(side);
7369     }
7370
7371     autoQueen = appData.alwaysPromoteToQueen;
7372
7373     if (fromX == -1) {
7374       int originalY = y;
7375       gatingPiece = EmptySquare;
7376       if (clickType != Press) {
7377         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7378             DragPieceEnd(xPix, yPix); dragging = 0;
7379             DrawPosition(FALSE, NULL);
7380         }
7381         return;
7382       }
7383       doubleClick = FALSE;
7384       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7385         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7386       }
7387       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7388       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7389          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7390          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7391             /* First square */
7392             if (OKToStartUserMove(fromX, fromY)) {
7393                 second = 0;
7394                 ReportClick("lift", x, y);
7395                 MarkTargetSquares(0);
7396                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7397                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7398                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7399                     promoSweep = defaultPromoChoice;
7400                     selectFlag = 0; lastX = xPix; lastY = yPix;
7401                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7402                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7403                 }
7404                 if (appData.highlightDragging) {
7405                     SetHighlights(fromX, fromY, -1, -1);
7406                 } else {
7407                     ClearHighlights();
7408                 }
7409             } else fromX = fromY = -1;
7410             return;
7411         }
7412     }
7413
7414     /* fromX != -1 */
7415     if (clickType == Press && gameMode != EditPosition) {
7416         ChessSquare fromP;
7417         ChessSquare toP;
7418         int frc;
7419
7420         // ignore off-board to clicks
7421         if(y < 0 || x < 0) return;
7422
7423         /* Check if clicking again on the same color piece */
7424         fromP = boards[currentMove][fromY][fromX];
7425         toP = boards[currentMove][y][x];
7426         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7427         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7428            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7429              WhitePawn <= toP && toP <= WhiteKing &&
7430              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7431              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7432             (BlackPawn <= fromP && fromP <= BlackKing &&
7433              BlackPawn <= toP && toP <= BlackKing &&
7434              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7435              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7436             /* Clicked again on same color piece -- changed his mind */
7437             second = (x == fromX && y == fromY);
7438             killX = killY = -1;
7439             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7440                 second = FALSE; // first double-click rather than scond click
7441                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7442             }
7443             promoDefaultAltered = FALSE;
7444             MarkTargetSquares(1);
7445            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7446             if (appData.highlightDragging) {
7447                 SetHighlights(x, y, -1, -1);
7448             } else {
7449                 ClearHighlights();
7450             }
7451             if (OKToStartUserMove(x, y)) {
7452                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7453                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7454                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7455                  gatingPiece = boards[currentMove][fromY][fromX];
7456                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7457                 fromX = x;
7458                 fromY = y; dragging = 1;
7459                 ReportClick("lift", x, y);
7460                 MarkTargetSquares(0);
7461                 DragPieceBegin(xPix, yPix, FALSE);
7462                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7463                     promoSweep = defaultPromoChoice;
7464                     selectFlag = 0; lastX = xPix; lastY = yPix;
7465                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7466                 }
7467             }
7468            }
7469            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7470            second = FALSE;
7471         }
7472         // ignore clicks on holdings
7473         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7474     }
7475
7476     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7477         DragPieceEnd(xPix, yPix); dragging = 0;
7478         if(clearFlag) {
7479             // a deferred attempt to click-click move an empty square on top of a piece
7480             boards[currentMove][y][x] = EmptySquare;
7481             ClearHighlights();
7482             DrawPosition(FALSE, boards[currentMove]);
7483             fromX = fromY = -1; clearFlag = 0;
7484             return;
7485         }
7486         if (appData.animateDragging) {
7487             /* Undo animation damage if any */
7488             DrawPosition(FALSE, NULL);
7489         }
7490         if (second || sweepSelecting) {
7491             /* Second up/down in same square; just abort move */
7492             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7493             second = sweepSelecting = 0;
7494             fromX = fromY = -1;
7495             gatingPiece = EmptySquare;
7496             MarkTargetSquares(1);
7497             ClearHighlights();
7498             gotPremove = 0;
7499             ClearPremoveHighlights();
7500         } else {
7501             /* First upclick in same square; start click-click mode */
7502             SetHighlights(x, y, -1, -1);
7503         }
7504         return;
7505     }
7506
7507     clearFlag = 0;
7508
7509     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7510         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7511         DisplayMessage(_("only marked squares are legal"),"");
7512         DrawPosition(TRUE, NULL);
7513         return; // ignore to-click
7514     }
7515
7516     /* we now have a different from- and (possibly off-board) to-square */
7517     /* Completed move */
7518     if(!sweepSelecting) {
7519         toX = x;
7520         toY = y;
7521     }
7522
7523     piece = boards[currentMove][fromY][fromX];
7524
7525     saveAnimate = appData.animate;
7526     if (clickType == Press) {
7527         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7528         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7529             // must be Edit Position mode with empty-square selected
7530             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7531             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7532             return;
7533         }
7534         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7535             return;
7536         }
7537         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7538             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7539         } else
7540         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7541         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7542           if(appData.sweepSelect) {
7543             promoSweep = defaultPromoChoice;
7544             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7545             selectFlag = 0; lastX = xPix; lastY = yPix;
7546             Sweep(0); // Pawn that is going to promote: preview promotion piece
7547             sweepSelecting = 1;
7548             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7549             MarkTargetSquares(1);
7550           }
7551           return; // promo popup appears on up-click
7552         }
7553         /* Finish clickclick move */
7554         if (appData.animate || appData.highlightLastMove) {
7555             SetHighlights(fromX, fromY, toX, toY);
7556         } else {
7557             ClearHighlights();
7558         }
7559     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7560         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7561         if (appData.animate || appData.highlightLastMove) {
7562             SetHighlights(fromX, fromY, toX, toY);
7563         } else {
7564             ClearHighlights();
7565         }
7566     } else {
7567 #if 0
7568 // [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
7569         /* Finish drag move */
7570         if (appData.highlightLastMove) {
7571             SetHighlights(fromX, fromY, toX, toY);
7572         } else {
7573             ClearHighlights();
7574         }
7575 #endif
7576         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7577         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7578           dragging *= 2;            // flag button-less dragging if we are dragging
7579           MarkTargetSquares(1);
7580           if(x == killX && y == killY) killX = killY = -1; else {
7581             killX = x; killY = y;     //remeber this square as intermediate
7582             ReportClick("put", x, y); // and inform engine
7583             ReportClick("lift", x, y);
7584             MarkTargetSquares(0);
7585             return;
7586           }
7587         }
7588         DragPieceEnd(xPix, yPix); dragging = 0;
7589         /* Don't animate move and drag both */
7590         appData.animate = FALSE;
7591     }
7592
7593     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7594     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7595         ChessSquare piece = boards[currentMove][fromY][fromX];
7596         if(gameMode == EditPosition && piece != EmptySquare &&
7597            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7598             int n;
7599
7600             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7601                 n = PieceToNumber(piece - (int)BlackPawn);
7602                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7603                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7604                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7605             } else
7606             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7607                 n = PieceToNumber(piece);
7608                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7609                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7610                 boards[currentMove][n][BOARD_WIDTH-2]++;
7611             }
7612             boards[currentMove][fromY][fromX] = EmptySquare;
7613         }
7614         ClearHighlights();
7615         fromX = fromY = -1;
7616         MarkTargetSquares(1);
7617         DrawPosition(TRUE, boards[currentMove]);
7618         return;
7619     }
7620
7621     // off-board moves should not be highlighted
7622     if(x < 0 || y < 0) ClearHighlights();
7623     else ReportClick("put", x, y);
7624
7625     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7626
7627     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7628         SetHighlights(fromX, fromY, toX, toY);
7629         MarkTargetSquares(1);
7630         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7631             // [HGM] super: promotion to captured piece selected from holdings
7632             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7633             promotionChoice = TRUE;
7634             // kludge follows to temporarily execute move on display, without promoting yet
7635             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7636             boards[currentMove][toY][toX] = p;
7637             DrawPosition(FALSE, boards[currentMove]);
7638             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7639             boards[currentMove][toY][toX] = q;
7640             DisplayMessage("Click in holdings to choose piece", "");
7641             return;
7642         }
7643         PromotionPopUp(promoChoice);
7644     } else {
7645         int oldMove = currentMove;
7646         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7647         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7648         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7649         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7650            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7651             DrawPosition(TRUE, boards[currentMove]);
7652         MarkTargetSquares(1);
7653         fromX = fromY = -1;
7654     }
7655     appData.animate = saveAnimate;
7656     if (appData.animate || appData.animateDragging) {
7657         /* Undo animation damage if needed */
7658         DrawPosition(FALSE, NULL);
7659     }
7660 }
7661
7662 int
7663 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7664 {   // front-end-free part taken out of PieceMenuPopup
7665     int whichMenu; int xSqr, ySqr;
7666
7667     if(seekGraphUp) { // [HGM] seekgraph
7668         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7669         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7670         return -2;
7671     }
7672
7673     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7674          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7675         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7676         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7677         if(action == Press)   {
7678             originalFlip = flipView;
7679             flipView = !flipView; // temporarily flip board to see game from partners perspective
7680             DrawPosition(TRUE, partnerBoard);
7681             DisplayMessage(partnerStatus, "");
7682             partnerUp = TRUE;
7683         } else if(action == Release) {
7684             flipView = originalFlip;
7685             DrawPosition(TRUE, boards[currentMove]);
7686             partnerUp = FALSE;
7687         }
7688         return -2;
7689     }
7690
7691     xSqr = EventToSquare(x, BOARD_WIDTH);
7692     ySqr = EventToSquare(y, BOARD_HEIGHT);
7693     if (action == Release) {
7694         if(pieceSweep != EmptySquare) {
7695             EditPositionMenuEvent(pieceSweep, toX, toY);
7696             pieceSweep = EmptySquare;
7697         } else UnLoadPV(); // [HGM] pv
7698     }
7699     if (action != Press) return -2; // return code to be ignored
7700     switch (gameMode) {
7701       case IcsExamining:
7702         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7703       case EditPosition:
7704         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7705         if (xSqr < 0 || ySqr < 0) return -1;
7706         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7707         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7708         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7709         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7710         NextPiece(0);
7711         return 2; // grab
7712       case IcsObserving:
7713         if(!appData.icsEngineAnalyze) return -1;
7714       case IcsPlayingWhite:
7715       case IcsPlayingBlack:
7716         if(!appData.zippyPlay) goto noZip;
7717       case AnalyzeMode:
7718       case AnalyzeFile:
7719       case MachinePlaysWhite:
7720       case MachinePlaysBlack:
7721       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7722         if (!appData.dropMenu) {
7723           LoadPV(x, y);
7724           return 2; // flag front-end to grab mouse events
7725         }
7726         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7727            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7728       case EditGame:
7729       noZip:
7730         if (xSqr < 0 || ySqr < 0) return -1;
7731         if (!appData.dropMenu || appData.testLegality &&
7732             gameInfo.variant != VariantBughouse &&
7733             gameInfo.variant != VariantCrazyhouse) return -1;
7734         whichMenu = 1; // drop menu
7735         break;
7736       default:
7737         return -1;
7738     }
7739
7740     if (((*fromX = xSqr) < 0) ||
7741         ((*fromY = ySqr) < 0)) {
7742         *fromX = *fromY = -1;
7743         return -1;
7744     }
7745     if (flipView)
7746       *fromX = BOARD_WIDTH - 1 - *fromX;
7747     else
7748       *fromY = BOARD_HEIGHT - 1 - *fromY;
7749
7750     return whichMenu;
7751 }
7752
7753 void
7754 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7755 {
7756 //    char * hint = lastHint;
7757     FrontEndProgramStats stats;
7758
7759     stats.which = cps == &first ? 0 : 1;
7760     stats.depth = cpstats->depth;
7761     stats.nodes = cpstats->nodes;
7762     stats.score = cpstats->score;
7763     stats.time = cpstats->time;
7764     stats.pv = cpstats->movelist;
7765     stats.hint = lastHint;
7766     stats.an_move_index = 0;
7767     stats.an_move_count = 0;
7768
7769     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7770         stats.hint = cpstats->move_name;
7771         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7772         stats.an_move_count = cpstats->nr_moves;
7773     }
7774
7775     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
7776
7777     SetProgramStats( &stats );
7778 }
7779
7780 void
7781 ClearEngineOutputPane (int which)
7782 {
7783     static FrontEndProgramStats dummyStats;
7784     dummyStats.which = which;
7785     dummyStats.pv = "#";
7786     SetProgramStats( &dummyStats );
7787 }
7788
7789 #define MAXPLAYERS 500
7790
7791 char *
7792 TourneyStandings (int display)
7793 {
7794     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7795     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7796     char result, *p, *names[MAXPLAYERS];
7797
7798     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7799         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7800     names[0] = p = strdup(appData.participants);
7801     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7802
7803     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7804
7805     while(result = appData.results[nr]) {
7806         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7807         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7808         wScore = bScore = 0;
7809         switch(result) {
7810           case '+': wScore = 2; break;
7811           case '-': bScore = 2; break;
7812           case '=': wScore = bScore = 1; break;
7813           case ' ':
7814           case '*': return strdup("busy"); // tourney not finished
7815         }
7816         score[w] += wScore;
7817         score[b] += bScore;
7818         games[w]++;
7819         games[b]++;
7820         nr++;
7821     }
7822     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7823     for(w=0; w<nPlayers; w++) {
7824         bScore = -1;
7825         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7826         ranking[w] = b; points[w] = bScore; score[b] = -2;
7827     }
7828     p = malloc(nPlayers*34+1);
7829     for(w=0; w<nPlayers && w<display; w++)
7830         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7831     free(names[0]);
7832     return p;
7833 }
7834
7835 void
7836 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7837 {       // count all piece types
7838         int p, f, r;
7839         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7840         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7841         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7842                 p = board[r][f];
7843                 pCnt[p]++;
7844                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7845                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7846                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7847                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7848                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7849                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7850         }
7851 }
7852
7853 int
7854 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7855 {
7856         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7857         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7858
7859         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7860         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7861         if(myPawns == 2 && nMine == 3) // KPP
7862             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7863         if(myPawns == 1 && nMine == 2) // KP
7864             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7865         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7866             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7867         if(myPawns) return FALSE;
7868         if(pCnt[WhiteRook+side])
7869             return pCnt[BlackRook-side] ||
7870                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7871                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7872                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7873         if(pCnt[WhiteCannon+side]) {
7874             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7875             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7876         }
7877         if(pCnt[WhiteKnight+side])
7878             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7879         return FALSE;
7880 }
7881
7882 int
7883 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7884 {
7885         VariantClass v = gameInfo.variant;
7886
7887         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7888         if(v == VariantShatranj) return TRUE; // always winnable through baring
7889         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7890         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7891
7892         if(v == VariantXiangqi) {
7893                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7894
7895                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7896                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7897                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7898                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7899                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7900                 if(stale) // we have at least one last-rank P plus perhaps C
7901                     return majors // KPKX
7902                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7903                 else // KCA*E*
7904                     return pCnt[WhiteFerz+side] // KCAK
7905                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7906                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7907                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7908
7909         } else if(v == VariantKnightmate) {
7910                 if(nMine == 1) return FALSE;
7911                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7912         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7913                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7914
7915                 if(nMine == 1) return FALSE; // bare King
7916                 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
7917                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7918                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7919                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7920                 if(pCnt[WhiteKnight+side])
7921                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7922                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7923                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7924                 if(nBishops)
7925                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7926                 if(pCnt[WhiteAlfil+side])
7927                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7928                 if(pCnt[WhiteWazir+side])
7929                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7930         }
7931
7932         return TRUE;
7933 }
7934
7935 int
7936 CompareWithRights (Board b1, Board b2)
7937 {
7938     int rights = 0;
7939     if(!CompareBoards(b1, b2)) return FALSE;
7940     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7941     /* compare castling rights */
7942     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7943            rights++; /* King lost rights, while rook still had them */
7944     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7945         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7946            rights++; /* but at least one rook lost them */
7947     }
7948     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7949            rights++;
7950     if( b1[CASTLING][5] != NoRights ) {
7951         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7952            rights++;
7953     }
7954     return rights == 0;
7955 }
7956
7957 int
7958 Adjudicate (ChessProgramState *cps)
7959 {       // [HGM] some adjudications useful with buggy engines
7960         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7961         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7962         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7963         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7964         int k, drop, count = 0; static int bare = 1;
7965         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7966         Boolean canAdjudicate = !appData.icsActive;
7967
7968         // most tests only when we understand the game, i.e. legality-checking on
7969             if( appData.testLegality )
7970             {   /* [HGM] Some more adjudications for obstinate engines */
7971                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7972                 static int moveCount = 6;
7973                 ChessMove result;
7974                 char *reason = NULL;
7975
7976                 /* Count what is on board. */
7977                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7978
7979                 /* Some material-based adjudications that have to be made before stalemate test */
7980                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7981                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7982                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7983                      if(canAdjudicate && appData.checkMates) {
7984                          if(engineOpponent)
7985                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7986                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7987                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7988                          return 1;
7989                      }
7990                 }
7991
7992                 /* Bare King in Shatranj (loses) or Losers (wins) */
7993                 if( nrW == 1 || nrB == 1) {
7994                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7995                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7996                      if(canAdjudicate && appData.checkMates) {
7997                          if(engineOpponent)
7998                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7999                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8000                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8001                          return 1;
8002                      }
8003                   } else
8004                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8005                   {    /* bare King */
8006                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8007                         if(canAdjudicate && appData.checkMates) {
8008                             /* but only adjudicate if adjudication enabled */
8009                             if(engineOpponent)
8010                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8011                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8012                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8013                             return 1;
8014                         }
8015                   }
8016                 } else bare = 1;
8017
8018
8019             // don't wait for engine to announce game end if we can judge ourselves
8020             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8021               case MT_CHECK:
8022                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8023                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8024                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8025                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8026                             checkCnt++;
8027                         if(checkCnt >= 2) {
8028                             reason = "Xboard adjudication: 3rd check";
8029                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8030                             break;
8031                         }
8032                     }
8033                 }
8034               case MT_NONE:
8035               default:
8036                 break;
8037               case MT_STALEMATE:
8038               case MT_STAINMATE:
8039                 reason = "Xboard adjudication: Stalemate";
8040                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8041                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8042                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8043                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8044                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8045                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8046                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8047                                                                         EP_CHECKMATE : EP_WINS);
8048                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8049                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8050                 }
8051                 break;
8052               case MT_CHECKMATE:
8053                 reason = "Xboard adjudication: Checkmate";
8054                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8055                 if(gameInfo.variant == VariantShogi) {
8056                     if(forwardMostMove > backwardMostMove
8057                        && moveList[forwardMostMove-1][1] == '@'
8058                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8059                         reason = "XBoard adjudication: pawn-drop mate";
8060                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8061                     }
8062                 }
8063                 break;
8064             }
8065
8066                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8067                     case EP_STALEMATE:
8068                         result = GameIsDrawn; break;
8069                     case EP_CHECKMATE:
8070                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8071                     case EP_WINS:
8072                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8073                     default:
8074                         result = EndOfFile;
8075                 }
8076                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8077                     if(engineOpponent)
8078                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8079                     GameEnds( result, reason, GE_XBOARD );
8080                     return 1;
8081                 }
8082
8083                 /* Next absolutely insufficient mating material. */
8084                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8085                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8086                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8087
8088                      /* always flag draws, for judging claims */
8089                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8090
8091                      if(canAdjudicate && appData.materialDraws) {
8092                          /* but only adjudicate them if adjudication enabled */
8093                          if(engineOpponent) {
8094                            SendToProgram("force\n", engineOpponent); // suppress reply
8095                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8096                          }
8097                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8098                          return 1;
8099                      }
8100                 }
8101
8102                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8103                 if(gameInfo.variant == VariantXiangqi ?
8104                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8105                  : nrW + nrB == 4 &&
8106                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8107                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8108                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8109                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8110                    ) ) {
8111                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8112                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8113                           if(engineOpponent) {
8114                             SendToProgram("force\n", engineOpponent); // suppress reply
8115                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8116                           }
8117                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8118                           return 1;
8119                      }
8120                 } else moveCount = 6;
8121             }
8122
8123         // Repetition draws and 50-move rule can be applied independently of legality testing
8124
8125                 /* Check for rep-draws */
8126                 count = 0;
8127                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8128                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8129                 for(k = forwardMostMove-2;
8130                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8131                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8132                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8133                     k-=2)
8134                 {   int rights=0;
8135                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8136                         /* compare castling rights */
8137                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8138                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8139                                 rights++; /* King lost rights, while rook still had them */
8140                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8141                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8142                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8143                                    rights++; /* but at least one rook lost them */
8144                         }
8145                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8146                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8147                                 rights++;
8148                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8149                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8150                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8151                                    rights++;
8152                         }
8153                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8154                             && appData.drawRepeats > 1) {
8155                              /* adjudicate after user-specified nr of repeats */
8156                              int result = GameIsDrawn;
8157                              char *details = "XBoard adjudication: repetition draw";
8158                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8159                                 // [HGM] xiangqi: check for forbidden perpetuals
8160                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8161                                 for(m=forwardMostMove; m>k; m-=2) {
8162                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8163                                         ourPerpetual = 0; // the current mover did not always check
8164                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8165                                         hisPerpetual = 0; // the opponent did not always check
8166                                 }
8167                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8168                                                                         ourPerpetual, hisPerpetual);
8169                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8170                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8171                                     details = "Xboard adjudication: perpetual checking";
8172                                 } else
8173                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8174                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8175                                 } else
8176                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8177                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8178                                         result = BlackWins;
8179                                         details = "Xboard adjudication: repetition";
8180                                     }
8181                                 } else // it must be XQ
8182                                 // Now check for perpetual chases
8183                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8184                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8185                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8186                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8187                                         static char resdet[MSG_SIZ];
8188                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8189                                         details = resdet;
8190                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8191                                     } else
8192                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8193                                         break; // Abort repetition-checking loop.
8194                                 }
8195                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8196                              }
8197                              if(engineOpponent) {
8198                                SendToProgram("force\n", engineOpponent); // suppress reply
8199                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8200                              }
8201                              GameEnds( result, details, GE_XBOARD );
8202                              return 1;
8203                         }
8204                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8205                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8206                     }
8207                 }
8208
8209                 /* Now we test for 50-move draws. Determine ply count */
8210                 count = forwardMostMove;
8211                 /* look for last irreversble move */
8212                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8213                     count--;
8214                 /* if we hit starting position, add initial plies */
8215                 if( count == backwardMostMove )
8216                     count -= initialRulePlies;
8217                 count = forwardMostMove - count;
8218                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8219                         // adjust reversible move counter for checks in Xiangqi
8220                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8221                         if(i < backwardMostMove) i = backwardMostMove;
8222                         while(i <= forwardMostMove) {
8223                                 lastCheck = inCheck; // check evasion does not count
8224                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8225                                 if(inCheck || lastCheck) count--; // check does not count
8226                                 i++;
8227                         }
8228                 }
8229                 if( count >= 100)
8230                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8231                          /* this is used to judge if draw claims are legal */
8232                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8233                          if(engineOpponent) {
8234                            SendToProgram("force\n", engineOpponent); // suppress reply
8235                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8236                          }
8237                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8238                          return 1;
8239                 }
8240
8241                 /* if draw offer is pending, treat it as a draw claim
8242                  * when draw condition present, to allow engines a way to
8243                  * claim draws before making their move to avoid a race
8244                  * condition occurring after their move
8245                  */
8246                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8247                          char *p = NULL;
8248                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8249                              p = "Draw claim: 50-move rule";
8250                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8251                              p = "Draw claim: 3-fold repetition";
8252                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8253                              p = "Draw claim: insufficient mating material";
8254                          if( p != NULL && canAdjudicate) {
8255                              if(engineOpponent) {
8256                                SendToProgram("force\n", engineOpponent); // suppress reply
8257                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8258                              }
8259                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8260                              return 1;
8261                          }
8262                 }
8263
8264                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8265                     if(engineOpponent) {
8266                       SendToProgram("force\n", engineOpponent); // suppress reply
8267                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8268                     }
8269                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8270                     return 1;
8271                 }
8272         return 0;
8273 }
8274
8275 char *
8276 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8277 {   // [HGM] book: this routine intercepts moves to simulate book replies
8278     char *bookHit = NULL;
8279
8280     //first determine if the incoming move brings opponent into his book
8281     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8282         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8283     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8284     if(bookHit != NULL && !cps->bookSuspend) {
8285         // make sure opponent is not going to reply after receiving move to book position
8286         SendToProgram("force\n", cps);
8287         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8288     }
8289     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8290     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8291     // now arrange restart after book miss
8292     if(bookHit) {
8293         // after a book hit we never send 'go', and the code after the call to this routine
8294         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8295         char buf[MSG_SIZ], *move = bookHit;
8296         if(cps->useSAN) {
8297             int fromX, fromY, toX, toY;
8298             char promoChar;
8299             ChessMove moveType;
8300             move = buf + 30;
8301             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8302                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8303                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8304                                     PosFlags(forwardMostMove),
8305                                     fromY, fromX, toY, toX, promoChar, move);
8306             } else {
8307                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8308                 bookHit = NULL;
8309             }
8310         }
8311         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8312         SendToProgram(buf, cps);
8313         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8314     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8315         SendToProgram("go\n", cps);
8316         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8317     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8318         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8319             SendToProgram("go\n", cps);
8320         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8321     }
8322     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8323 }
8324
8325 int
8326 LoadError (char *errmess, ChessProgramState *cps)
8327 {   // unloads engine and switches back to -ncp mode if it was first
8328     if(cps->initDone) return FALSE;
8329     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8330     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8331     cps->pr = NoProc;
8332     if(cps == &first) {
8333         appData.noChessProgram = TRUE;
8334         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8335         gameMode = BeginningOfGame; ModeHighlight();
8336         SetNCPMode();
8337     }
8338     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8339     DisplayMessage("", ""); // erase waiting message
8340     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8341     return TRUE;
8342 }
8343
8344 char *savedMessage;
8345 ChessProgramState *savedState;
8346 void
8347 DeferredBookMove (void)
8348 {
8349         if(savedState->lastPing != savedState->lastPong)
8350                     ScheduleDelayedEvent(DeferredBookMove, 10);
8351         else
8352         HandleMachineMove(savedMessage, savedState);
8353 }
8354
8355 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8356 static ChessProgramState *stalledEngine;
8357 static char stashedInputMove[MSG_SIZ];
8358
8359 void
8360 HandleMachineMove (char *message, ChessProgramState *cps)
8361 {
8362     static char firstLeg[20];
8363     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8364     char realname[MSG_SIZ];
8365     int fromX, fromY, toX, toY;
8366     ChessMove moveType;
8367     char promoChar, roar;
8368     char *p, *pv=buf1;
8369     int machineWhite, oldError;
8370     char *bookHit;
8371
8372     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8373         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8374         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8375             DisplayError(_("Invalid pairing from pairing engine"), 0);
8376             return;
8377         }
8378         pairingReceived = 1;
8379         NextMatchGame();
8380         return; // Skim the pairing messages here.
8381     }
8382
8383     oldError = cps->userError; cps->userError = 0;
8384
8385 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8386     /*
8387      * Kludge to ignore BEL characters
8388      */
8389     while (*message == '\007') message++;
8390
8391     /*
8392      * [HGM] engine debug message: ignore lines starting with '#' character
8393      */
8394     if(cps->debug && *message == '#') return;
8395
8396     /*
8397      * Look for book output
8398      */
8399     if (cps == &first && bookRequested) {
8400         if (message[0] == '\t' || message[0] == ' ') {
8401             /* Part of the book output is here; append it */
8402             strcat(bookOutput, message);
8403             strcat(bookOutput, "  \n");
8404             return;
8405         } else if (bookOutput[0] != NULLCHAR) {
8406             /* All of book output has arrived; display it */
8407             char *p = bookOutput;
8408             while (*p != NULLCHAR) {
8409                 if (*p == '\t') *p = ' ';
8410                 p++;
8411             }
8412             DisplayInformation(bookOutput);
8413             bookRequested = FALSE;
8414             /* Fall through to parse the current output */
8415         }
8416     }
8417
8418     /*
8419      * Look for machine move.
8420      */
8421     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8422         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8423     {
8424         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8425             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8426             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8427             stalledEngine = cps;
8428             if(appData.ponderNextMove) { // bring opponent out of ponder
8429                 if(gameMode == TwoMachinesPlay) {
8430                     if(cps->other->pause)
8431                         PauseEngine(cps->other);
8432                     else
8433                         SendToProgram("easy\n", cps->other);
8434                 }
8435             }
8436             StopClocks();
8437             return;
8438         }
8439
8440         /* This method is only useful on engines that support ping */
8441         if (cps->lastPing != cps->lastPong) {
8442           if (gameMode == BeginningOfGame) {
8443             /* Extra move from before last new; ignore */
8444             if (appData.debugMode) {
8445                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8446             }
8447           } else {
8448             if (appData.debugMode) {
8449                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8450                         cps->which, gameMode);
8451             }
8452
8453             SendToProgram("undo\n", cps);
8454           }
8455           return;
8456         }
8457
8458         switch (gameMode) {
8459           case BeginningOfGame:
8460             /* Extra move from before last reset; ignore */
8461             if (appData.debugMode) {
8462                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8463             }
8464             return;
8465
8466           case EndOfGame:
8467           case IcsIdle:
8468           default:
8469             /* Extra move after we tried to stop.  The mode test is
8470                not a reliable way of detecting this problem, but it's
8471                the best we can do on engines that don't support ping.
8472             */
8473             if (appData.debugMode) {
8474                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8475                         cps->which, gameMode);
8476             }
8477             SendToProgram("undo\n", cps);
8478             return;
8479
8480           case MachinePlaysWhite:
8481           case IcsPlayingWhite:
8482             machineWhite = TRUE;
8483             break;
8484
8485           case MachinePlaysBlack:
8486           case IcsPlayingBlack:
8487             machineWhite = FALSE;
8488             break;
8489
8490           case TwoMachinesPlay:
8491             machineWhite = (cps->twoMachinesColor[0] == 'w');
8492             break;
8493         }
8494         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8495             if (appData.debugMode) {
8496                 fprintf(debugFP,
8497                         "Ignoring move out of turn by %s, gameMode %d"
8498                         ", forwardMost %d\n",
8499                         cps->which, gameMode, forwardMostMove);
8500             }
8501             return;
8502         }
8503
8504         if(cps->alphaRank) AlphaRank(machineMove, 4);
8505
8506         // [HGM] lion: (some very limited) support for Alien protocol
8507         killX = killY = -1;
8508         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8509             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8510             return;
8511         } else if(firstLeg[0]) { // there was a previous leg;
8512             // only support case where same piece makes two step (and don't even test that!)
8513             char buf[20], *p = machineMove+1, *q = buf+1, f;
8514             safeStrCpy(buf, machineMove, 20);
8515             while(isdigit(*q)) q++; // find start of to-square
8516             safeStrCpy(machineMove, firstLeg, 20);
8517             while(isdigit(*p)) p++;
8518             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8519             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8520             firstLeg[0] = NULLCHAR;
8521         }
8522
8523         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8524                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8525             /* Machine move could not be parsed; ignore it. */
8526           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8527                     machineMove, _(cps->which));
8528             DisplayMoveError(buf1);
8529             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8530                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8531             if (gameMode == TwoMachinesPlay) {
8532               GameEnds(machineWhite ? BlackWins : WhiteWins,
8533                        buf1, GE_XBOARD);
8534             }
8535             return;
8536         }
8537
8538         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8539         /* So we have to redo legality test with true e.p. status here,  */
8540         /* to make sure an illegal e.p. capture does not slip through,   */
8541         /* to cause a forfeit on a justified illegal-move complaint      */
8542         /* of the opponent.                                              */
8543         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8544            ChessMove moveType;
8545            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8546                              fromY, fromX, toY, toX, promoChar);
8547             if(moveType == IllegalMove) {
8548               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8549                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8550                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8551                            buf1, GE_XBOARD);
8552                 return;
8553            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8554            /* [HGM] Kludge to handle engines that send FRC-style castling
8555               when they shouldn't (like TSCP-Gothic) */
8556            switch(moveType) {
8557              case WhiteASideCastleFR:
8558              case BlackASideCastleFR:
8559                toX+=2;
8560                currentMoveString[2]++;
8561                break;
8562              case WhiteHSideCastleFR:
8563              case BlackHSideCastleFR:
8564                toX--;
8565                currentMoveString[2]--;
8566                break;
8567              default: ; // nothing to do, but suppresses warning of pedantic compilers
8568            }
8569         }
8570         hintRequested = FALSE;
8571         lastHint[0] = NULLCHAR;
8572         bookRequested = FALSE;
8573         /* Program may be pondering now */
8574         cps->maybeThinking = TRUE;
8575         if (cps->sendTime == 2) cps->sendTime = 1;
8576         if (cps->offeredDraw) cps->offeredDraw--;
8577
8578         /* [AS] Save move info*/
8579         pvInfoList[ forwardMostMove ].score = programStats.score;
8580         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8581         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8582
8583         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8584
8585         /* Test suites abort the 'game' after one move */
8586         if(*appData.finger) {
8587            static FILE *f;
8588            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8589            if(!f) f = fopen(appData.finger, "w");
8590            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8591            else { DisplayFatalError("Bad output file", errno, 0); return; }
8592            free(fen);
8593            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8594         }
8595
8596         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8597         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8598             int count = 0;
8599
8600             while( count < adjudicateLossPlies ) {
8601                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8602
8603                 if( count & 1 ) {
8604                     score = -score; /* Flip score for winning side */
8605                 }
8606
8607                 if( score > adjudicateLossThreshold ) {
8608                     break;
8609                 }
8610
8611                 count++;
8612             }
8613
8614             if( count >= adjudicateLossPlies ) {
8615                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8616
8617                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8618                     "Xboard adjudication",
8619                     GE_XBOARD );
8620
8621                 return;
8622             }
8623         }
8624
8625         if(Adjudicate(cps)) {
8626             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8627             return; // [HGM] adjudicate: for all automatic game ends
8628         }
8629
8630 #if ZIPPY
8631         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8632             first.initDone) {
8633           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8634                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8635                 SendToICS("draw ");
8636                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8637           }
8638           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8639           ics_user_moved = 1;
8640           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8641                 char buf[3*MSG_SIZ];
8642
8643                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8644                         programStats.score / 100.,
8645                         programStats.depth,
8646                         programStats.time / 100.,
8647                         (unsigned int)programStats.nodes,
8648                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8649                         programStats.movelist);
8650                 SendToICS(buf);
8651 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8652           }
8653         }
8654 #endif
8655
8656         /* [AS] Clear stats for next move */
8657         ClearProgramStats();
8658         thinkOutput[0] = NULLCHAR;
8659         hiddenThinkOutputState = 0;
8660
8661         bookHit = NULL;
8662         if (gameMode == TwoMachinesPlay) {
8663             /* [HGM] relaying draw offers moved to after reception of move */
8664             /* and interpreting offer as claim if it brings draw condition */
8665             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8666                 SendToProgram("draw\n", cps->other);
8667             }
8668             if (cps->other->sendTime) {
8669                 SendTimeRemaining(cps->other,
8670                                   cps->other->twoMachinesColor[0] == 'w');
8671             }
8672             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8673             if (firstMove && !bookHit) {
8674                 firstMove = FALSE;
8675                 if (cps->other->useColors) {
8676                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8677                 }
8678                 SendToProgram("go\n", cps->other);
8679             }
8680             cps->other->maybeThinking = TRUE;
8681         }
8682
8683         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8684
8685         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8686
8687         if (!pausing && appData.ringBellAfterMoves) {
8688             if(!roar) RingBell();
8689         }
8690
8691         /*
8692          * Reenable menu items that were disabled while
8693          * machine was thinking
8694          */
8695         if (gameMode != TwoMachinesPlay)
8696             SetUserThinkingEnables();
8697
8698         // [HGM] book: after book hit opponent has received move and is now in force mode
8699         // force the book reply into it, and then fake that it outputted this move by jumping
8700         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8701         if(bookHit) {
8702                 static char bookMove[MSG_SIZ]; // a bit generous?
8703
8704                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8705                 strcat(bookMove, bookHit);
8706                 message = bookMove;
8707                 cps = cps->other;
8708                 programStats.nodes = programStats.depth = programStats.time =
8709                 programStats.score = programStats.got_only_move = 0;
8710                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8711
8712                 if(cps->lastPing != cps->lastPong) {
8713                     savedMessage = message; // args for deferred call
8714                     savedState = cps;
8715                     ScheduleDelayedEvent(DeferredBookMove, 10);
8716                     return;
8717                 }
8718                 goto FakeBookMove;
8719         }
8720
8721         return;
8722     }
8723
8724     /* Set special modes for chess engines.  Later something general
8725      *  could be added here; for now there is just one kludge feature,
8726      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8727      *  when "xboard" is given as an interactive command.
8728      */
8729     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8730         cps->useSigint = FALSE;
8731         cps->useSigterm = FALSE;
8732     }
8733     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8734       ParseFeatures(message+8, cps);
8735       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8736     }
8737
8738     if (!strncmp(message, "setup ", 6) && 
8739         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8740           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8741                                         ) { // [HGM] allow first engine to define opening position
8742       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8743       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8744       *buf = NULLCHAR;
8745       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8746       if(startedFromSetupPosition) return;
8747       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8748       if(dummy >= 3) {
8749         while(message[s] && message[s++] != ' ');
8750         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8751            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8752             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8753             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8754           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8755           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8756         }
8757       }
8758       ParseFEN(boards[0], &dummy, message+s, FALSE);
8759       DrawPosition(TRUE, boards[0]);
8760       startedFromSetupPosition = TRUE;
8761       return;
8762     }
8763     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8764      * want this, I was asked to put it in, and obliged.
8765      */
8766     if (!strncmp(message, "setboard ", 9)) {
8767         Board initial_position;
8768
8769         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8770
8771         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8772             DisplayError(_("Bad FEN received from engine"), 0);
8773             return ;
8774         } else {
8775            Reset(TRUE, FALSE);
8776            CopyBoard(boards[0], initial_position);
8777            initialRulePlies = FENrulePlies;
8778            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8779            else gameMode = MachinePlaysBlack;
8780            DrawPosition(FALSE, boards[currentMove]);
8781         }
8782         return;
8783     }
8784
8785     /*
8786      * Look for communication commands
8787      */
8788     if (!strncmp(message, "telluser ", 9)) {
8789         if(message[9] == '\\' && message[10] == '\\')
8790             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8791         PlayTellSound();
8792         DisplayNote(message + 9);
8793         return;
8794     }
8795     if (!strncmp(message, "tellusererror ", 14)) {
8796         cps->userError = 1;
8797         if(message[14] == '\\' && message[15] == '\\')
8798             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8799         PlayTellSound();
8800         DisplayError(message + 14, 0);
8801         return;
8802     }
8803     if (!strncmp(message, "tellopponent ", 13)) {
8804       if (appData.icsActive) {
8805         if (loggedOn) {
8806           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8807           SendToICS(buf1);
8808         }
8809       } else {
8810         DisplayNote(message + 13);
8811       }
8812       return;
8813     }
8814     if (!strncmp(message, "tellothers ", 11)) {
8815       if (appData.icsActive) {
8816         if (loggedOn) {
8817           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8818           SendToICS(buf1);
8819         }
8820       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8821       return;
8822     }
8823     if (!strncmp(message, "tellall ", 8)) {
8824       if (appData.icsActive) {
8825         if (loggedOn) {
8826           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8827           SendToICS(buf1);
8828         }
8829       } else {
8830         DisplayNote(message + 8);
8831       }
8832       return;
8833     }
8834     if (strncmp(message, "warning", 7) == 0) {
8835         /* Undocumented feature, use tellusererror in new code */
8836         DisplayError(message, 0);
8837         return;
8838     }
8839     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8840         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8841         strcat(realname, " query");
8842         AskQuestion(realname, buf2, buf1, cps->pr);
8843         return;
8844     }
8845     /* Commands from the engine directly to ICS.  We don't allow these to be
8846      *  sent until we are logged on. Crafty kibitzes have been known to
8847      *  interfere with the login process.
8848      */
8849     if (loggedOn) {
8850         if (!strncmp(message, "tellics ", 8)) {
8851             SendToICS(message + 8);
8852             SendToICS("\n");
8853             return;
8854         }
8855         if (!strncmp(message, "tellicsnoalias ", 15)) {
8856             SendToICS(ics_prefix);
8857             SendToICS(message + 15);
8858             SendToICS("\n");
8859             return;
8860         }
8861         /* The following are for backward compatibility only */
8862         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8863             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8864             SendToICS(ics_prefix);
8865             SendToICS(message);
8866             SendToICS("\n");
8867             return;
8868         }
8869     }
8870     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8871         if(initPing == cps->lastPong) {
8872             if(gameInfo.variant == VariantUnknown) {
8873                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8874                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8875                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8876             }
8877             initPing = -1;
8878         }
8879         return;
8880     }
8881     if(!strncmp(message, "highlight ", 10)) {
8882         if(appData.testLegality && appData.markers) return;
8883         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8884         return;
8885     }
8886     if(!strncmp(message, "click ", 6)) {
8887         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8888         if(appData.testLegality || !appData.oneClick) return;
8889         sscanf(message+6, "%c%d%c", &f, &y, &c);
8890         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8891         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8892         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8893         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8894         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8895         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8896             LeftClick(Release, lastLeftX, lastLeftY);
8897         controlKey  = (c == ',');
8898         LeftClick(Press, x, y);
8899         LeftClick(Release, x, y);
8900         first.highlight = f;
8901         return;
8902     }
8903     /*
8904      * If the move is illegal, cancel it and redraw the board.
8905      * Also deal with other error cases.  Matching is rather loose
8906      * here to accommodate engines written before the spec.
8907      */
8908     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8909         strncmp(message, "Error", 5) == 0) {
8910         if (StrStr(message, "name") ||
8911             StrStr(message, "rating") || StrStr(message, "?") ||
8912             StrStr(message, "result") || StrStr(message, "board") ||
8913             StrStr(message, "bk") || StrStr(message, "computer") ||
8914             StrStr(message, "variant") || StrStr(message, "hint") ||
8915             StrStr(message, "random") || StrStr(message, "depth") ||
8916             StrStr(message, "accepted")) {
8917             return;
8918         }
8919         if (StrStr(message, "protover")) {
8920           /* Program is responding to input, so it's apparently done
8921              initializing, and this error message indicates it is
8922              protocol version 1.  So we don't need to wait any longer
8923              for it to initialize and send feature commands. */
8924           FeatureDone(cps, 1);
8925           cps->protocolVersion = 1;
8926           return;
8927         }
8928         cps->maybeThinking = FALSE;
8929
8930         if (StrStr(message, "draw")) {
8931             /* Program doesn't have "draw" command */
8932             cps->sendDrawOffers = 0;
8933             return;
8934         }
8935         if (cps->sendTime != 1 &&
8936             (StrStr(message, "time") || StrStr(message, "otim"))) {
8937           /* Program apparently doesn't have "time" or "otim" command */
8938           cps->sendTime = 0;
8939           return;
8940         }
8941         if (StrStr(message, "analyze")) {
8942             cps->analysisSupport = FALSE;
8943             cps->analyzing = FALSE;
8944 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8945             EditGameEvent(); // [HGM] try to preserve loaded game
8946             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8947             DisplayError(buf2, 0);
8948             return;
8949         }
8950         if (StrStr(message, "(no matching move)st")) {
8951           /* Special kludge for GNU Chess 4 only */
8952           cps->stKludge = TRUE;
8953           SendTimeControl(cps, movesPerSession, timeControl,
8954                           timeIncrement, appData.searchDepth,
8955                           searchTime);
8956           return;
8957         }
8958         if (StrStr(message, "(no matching move)sd")) {
8959           /* Special kludge for GNU Chess 4 only */
8960           cps->sdKludge = TRUE;
8961           SendTimeControl(cps, movesPerSession, timeControl,
8962                           timeIncrement, appData.searchDepth,
8963                           searchTime);
8964           return;
8965         }
8966         if (!StrStr(message, "llegal")) {
8967             return;
8968         }
8969         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8970             gameMode == IcsIdle) return;
8971         if (forwardMostMove <= backwardMostMove) return;
8972         if (pausing) PauseEvent();
8973       if(appData.forceIllegal) {
8974             // [HGM] illegal: machine refused move; force position after move into it
8975           SendToProgram("force\n", cps);
8976           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8977                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8978                 // when black is to move, while there might be nothing on a2 or black
8979                 // might already have the move. So send the board as if white has the move.
8980                 // But first we must change the stm of the engine, as it refused the last move
8981                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8982                 if(WhiteOnMove(forwardMostMove)) {
8983                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8984                     SendBoard(cps, forwardMostMove); // kludgeless board
8985                 } else {
8986                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8987                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8988                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8989                 }
8990           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8991             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8992                  gameMode == TwoMachinesPlay)
8993               SendToProgram("go\n", cps);
8994             return;
8995       } else
8996         if (gameMode == PlayFromGameFile) {
8997             /* Stop reading this game file */
8998             gameMode = EditGame;
8999             ModeHighlight();
9000         }
9001         /* [HGM] illegal-move claim should forfeit game when Xboard */
9002         /* only passes fully legal moves                            */
9003         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9004             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9005                                 "False illegal-move claim", GE_XBOARD );
9006             return; // do not take back move we tested as valid
9007         }
9008         currentMove = forwardMostMove-1;
9009         DisplayMove(currentMove-1); /* before DisplayMoveError */
9010         SwitchClocks(forwardMostMove-1); // [HGM] race
9011         DisplayBothClocks();
9012         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9013                 parseList[currentMove], _(cps->which));
9014         DisplayMoveError(buf1);
9015         DrawPosition(FALSE, boards[currentMove]);
9016
9017         SetUserThinkingEnables();
9018         return;
9019     }
9020     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9021         /* Program has a broken "time" command that
9022            outputs a string not ending in newline.
9023            Don't use it. */
9024         cps->sendTime = 0;
9025     }
9026
9027     /*
9028      * If chess program startup fails, exit with an error message.
9029      * Attempts to recover here are futile. [HGM] Well, we try anyway
9030      */
9031     if ((StrStr(message, "unknown host") != NULL)
9032         || (StrStr(message, "No remote directory") != NULL)
9033         || (StrStr(message, "not found") != NULL)
9034         || (StrStr(message, "No such file") != NULL)
9035         || (StrStr(message, "can't alloc") != NULL)
9036         || (StrStr(message, "Permission denied") != NULL)) {
9037
9038         cps->maybeThinking = FALSE;
9039         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9040                 _(cps->which), cps->program, cps->host, message);
9041         RemoveInputSource(cps->isr);
9042         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9043             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9044             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9045         }
9046         return;
9047     }
9048
9049     /*
9050      * Look for hint output
9051      */
9052     if (sscanf(message, "Hint: %s", buf1) == 1) {
9053         if (cps == &first && hintRequested) {
9054             hintRequested = FALSE;
9055             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9056                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9057                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9058                                     PosFlags(forwardMostMove),
9059                                     fromY, fromX, toY, toX, promoChar, buf1);
9060                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9061                 DisplayInformation(buf2);
9062             } else {
9063                 /* Hint move could not be parsed!? */
9064               snprintf(buf2, sizeof(buf2),
9065                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9066                         buf1, _(cps->which));
9067                 DisplayError(buf2, 0);
9068             }
9069         } else {
9070           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9071         }
9072         return;
9073     }
9074
9075     /*
9076      * Ignore other messages if game is not in progress
9077      */
9078     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9079         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9080
9081     /*
9082      * look for win, lose, draw, or draw offer
9083      */
9084     if (strncmp(message, "1-0", 3) == 0) {
9085         char *p, *q, *r = "";
9086         p = strchr(message, '{');
9087         if (p) {
9088             q = strchr(p, '}');
9089             if (q) {
9090                 *q = NULLCHAR;
9091                 r = p + 1;
9092             }
9093         }
9094         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9095         return;
9096     } else if (strncmp(message, "0-1", 3) == 0) {
9097         char *p, *q, *r = "";
9098         p = strchr(message, '{');
9099         if (p) {
9100             q = strchr(p, '}');
9101             if (q) {
9102                 *q = NULLCHAR;
9103                 r = p + 1;
9104             }
9105         }
9106         /* Kludge for Arasan 4.1 bug */
9107         if (strcmp(r, "Black resigns") == 0) {
9108             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9109             return;
9110         }
9111         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9112         return;
9113     } else if (strncmp(message, "1/2", 3) == 0) {
9114         char *p, *q, *r = "";
9115         p = strchr(message, '{');
9116         if (p) {
9117             q = strchr(p, '}');
9118             if (q) {
9119                 *q = NULLCHAR;
9120                 r = p + 1;
9121             }
9122         }
9123
9124         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9125         return;
9126
9127     } else if (strncmp(message, "White resign", 12) == 0) {
9128         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9129         return;
9130     } else if (strncmp(message, "Black resign", 12) == 0) {
9131         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9132         return;
9133     } else if (strncmp(message, "White matches", 13) == 0 ||
9134                strncmp(message, "Black matches", 13) == 0   ) {
9135         /* [HGM] ignore GNUShogi noises */
9136         return;
9137     } else if (strncmp(message, "White", 5) == 0 &&
9138                message[5] != '(' &&
9139                StrStr(message, "Black") == NULL) {
9140         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9141         return;
9142     } else if (strncmp(message, "Black", 5) == 0 &&
9143                message[5] != '(') {
9144         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9145         return;
9146     } else if (strcmp(message, "resign") == 0 ||
9147                strcmp(message, "computer resigns") == 0) {
9148         switch (gameMode) {
9149           case MachinePlaysBlack:
9150           case IcsPlayingBlack:
9151             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9152             break;
9153           case MachinePlaysWhite:
9154           case IcsPlayingWhite:
9155             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9156             break;
9157           case TwoMachinesPlay:
9158             if (cps->twoMachinesColor[0] == 'w')
9159               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9160             else
9161               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9162             break;
9163           default:
9164             /* can't happen */
9165             break;
9166         }
9167         return;
9168     } else if (strncmp(message, "opponent mates", 14) == 0) {
9169         switch (gameMode) {
9170           case MachinePlaysBlack:
9171           case IcsPlayingBlack:
9172             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9173             break;
9174           case MachinePlaysWhite:
9175           case IcsPlayingWhite:
9176             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9177             break;
9178           case TwoMachinesPlay:
9179             if (cps->twoMachinesColor[0] == 'w')
9180               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9181             else
9182               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9183             break;
9184           default:
9185             /* can't happen */
9186             break;
9187         }
9188         return;
9189     } else if (strncmp(message, "computer mates", 14) == 0) {
9190         switch (gameMode) {
9191           case MachinePlaysBlack:
9192           case IcsPlayingBlack:
9193             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9194             break;
9195           case MachinePlaysWhite:
9196           case IcsPlayingWhite:
9197             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9198             break;
9199           case TwoMachinesPlay:
9200             if (cps->twoMachinesColor[0] == 'w')
9201               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9202             else
9203               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9204             break;
9205           default:
9206             /* can't happen */
9207             break;
9208         }
9209         return;
9210     } else if (strncmp(message, "checkmate", 9) == 0) {
9211         if (WhiteOnMove(forwardMostMove)) {
9212             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9213         } else {
9214             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9215         }
9216         return;
9217     } else if (strstr(message, "Draw") != NULL ||
9218                strstr(message, "game is a draw") != NULL) {
9219         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9220         return;
9221     } else if (strstr(message, "offer") != NULL &&
9222                strstr(message, "draw") != NULL) {
9223 #if ZIPPY
9224         if (appData.zippyPlay && first.initDone) {
9225             /* Relay offer to ICS */
9226             SendToICS(ics_prefix);
9227             SendToICS("draw\n");
9228         }
9229 #endif
9230         cps->offeredDraw = 2; /* valid until this engine moves twice */
9231         if (gameMode == TwoMachinesPlay) {
9232             if (cps->other->offeredDraw) {
9233                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9234             /* [HGM] in two-machine mode we delay relaying draw offer      */
9235             /* until after we also have move, to see if it is really claim */
9236             }
9237         } else if (gameMode == MachinePlaysWhite ||
9238                    gameMode == MachinePlaysBlack) {
9239           if (userOfferedDraw) {
9240             DisplayInformation(_("Machine accepts your draw offer"));
9241             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9242           } else {
9243             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9244           }
9245         }
9246     }
9247
9248
9249     /*
9250      * Look for thinking output
9251      */
9252     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9253           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9254                                 ) {
9255         int plylev, mvleft, mvtot, curscore, time;
9256         char mvname[MOVE_LEN];
9257         u64 nodes; // [DM]
9258         char plyext;
9259         int ignore = FALSE;
9260         int prefixHint = FALSE;
9261         mvname[0] = NULLCHAR;
9262
9263         switch (gameMode) {
9264           case MachinePlaysBlack:
9265           case IcsPlayingBlack:
9266             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9267             break;
9268           case MachinePlaysWhite:
9269           case IcsPlayingWhite:
9270             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9271             break;
9272           case AnalyzeMode:
9273           case AnalyzeFile:
9274             break;
9275           case IcsObserving: /* [DM] icsEngineAnalyze */
9276             if (!appData.icsEngineAnalyze) ignore = TRUE;
9277             break;
9278           case TwoMachinesPlay:
9279             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9280                 ignore = TRUE;
9281             }
9282             break;
9283           default:
9284             ignore = TRUE;
9285             break;
9286         }
9287
9288         if (!ignore) {
9289             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9290             buf1[0] = NULLCHAR;
9291             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9292                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9293
9294                 if (plyext != ' ' && plyext != '\t') {
9295                     time *= 100;
9296                 }
9297
9298                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9299                 if( cps->scoreIsAbsolute &&
9300                     ( gameMode == MachinePlaysBlack ||
9301                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9302                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9303                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9304                      !WhiteOnMove(currentMove)
9305                     ) )
9306                 {
9307                     curscore = -curscore;
9308                 }
9309
9310                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9311
9312                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9313                         char buf[MSG_SIZ];
9314                         FILE *f;
9315                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9316                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9317                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9318                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9319                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9320                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9321                                 fclose(f);
9322                         }
9323                         else
9324                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9325                           DisplayError(_("failed writing PV"), 0);
9326                 }
9327
9328                 tempStats.depth = plylev;
9329                 tempStats.nodes = nodes;
9330                 tempStats.time = time;
9331                 tempStats.score = curscore;
9332                 tempStats.got_only_move = 0;
9333
9334                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9335                         int ticklen;
9336
9337                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9338                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9339                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9340                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9341                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9342                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9343                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9344                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9345                 }
9346
9347                 /* Buffer overflow protection */
9348                 if (pv[0] != NULLCHAR) {
9349                     if (strlen(pv) >= sizeof(tempStats.movelist)
9350                         && appData.debugMode) {
9351                         fprintf(debugFP,
9352                                 "PV is too long; using the first %u bytes.\n",
9353                                 (unsigned) sizeof(tempStats.movelist) - 1);
9354                     }
9355
9356                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9357                 } else {
9358                     sprintf(tempStats.movelist, " no PV\n");
9359                 }
9360
9361                 if (tempStats.seen_stat) {
9362                     tempStats.ok_to_send = 1;
9363                 }
9364
9365                 if (strchr(tempStats.movelist, '(') != NULL) {
9366                     tempStats.line_is_book = 1;
9367                     tempStats.nr_moves = 0;
9368                     tempStats.moves_left = 0;
9369                 } else {
9370                     tempStats.line_is_book = 0;
9371                 }
9372
9373                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9374                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9375
9376                 SendProgramStatsToFrontend( cps, &tempStats );
9377
9378                 /*
9379                     [AS] Protect the thinkOutput buffer from overflow... this
9380                     is only useful if buf1 hasn't overflowed first!
9381                 */
9382                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9383                          plylev,
9384                          (gameMode == TwoMachinesPlay ?
9385                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9386                          ((double) curscore) / 100.0,
9387                          prefixHint ? lastHint : "",
9388                          prefixHint ? " " : "" );
9389
9390                 if( buf1[0] != NULLCHAR ) {
9391                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9392
9393                     if( strlen(pv) > max_len ) {
9394                         if( appData.debugMode) {
9395                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9396                         }
9397                         pv[max_len+1] = '\0';
9398                     }
9399
9400                     strcat( thinkOutput, pv);
9401                 }
9402
9403                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9404                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9405                     DisplayMove(currentMove - 1);
9406                 }
9407                 return;
9408
9409             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9410                 /* crafty (9.25+) says "(only move) <move>"
9411                  * if there is only 1 legal move
9412                  */
9413                 sscanf(p, "(only move) %s", buf1);
9414                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9415                 sprintf(programStats.movelist, "%s (only move)", buf1);
9416                 programStats.depth = 1;
9417                 programStats.nr_moves = 1;
9418                 programStats.moves_left = 1;
9419                 programStats.nodes = 1;
9420                 programStats.time = 1;
9421                 programStats.got_only_move = 1;
9422
9423                 /* Not really, but we also use this member to
9424                    mean "line isn't going to change" (Crafty
9425                    isn't searching, so stats won't change) */
9426                 programStats.line_is_book = 1;
9427
9428                 SendProgramStatsToFrontend( cps, &programStats );
9429
9430                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9431                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9432                     DisplayMove(currentMove - 1);
9433                 }
9434                 return;
9435             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9436                               &time, &nodes, &plylev, &mvleft,
9437                               &mvtot, mvname) >= 5) {
9438                 /* The stat01: line is from Crafty (9.29+) in response
9439                    to the "." command */
9440                 programStats.seen_stat = 1;
9441                 cps->maybeThinking = TRUE;
9442
9443                 if (programStats.got_only_move || !appData.periodicUpdates)
9444                   return;
9445
9446                 programStats.depth = plylev;
9447                 programStats.time = time;
9448                 programStats.nodes = nodes;
9449                 programStats.moves_left = mvleft;
9450                 programStats.nr_moves = mvtot;
9451                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9452                 programStats.ok_to_send = 1;
9453                 programStats.movelist[0] = '\0';
9454
9455                 SendProgramStatsToFrontend( cps, &programStats );
9456
9457                 return;
9458
9459             } else if (strncmp(message,"++",2) == 0) {
9460                 /* Crafty 9.29+ outputs this */
9461                 programStats.got_fail = 2;
9462                 return;
9463
9464             } else if (strncmp(message,"--",2) == 0) {
9465                 /* Crafty 9.29+ outputs this */
9466                 programStats.got_fail = 1;
9467                 return;
9468
9469             } else if (thinkOutput[0] != NULLCHAR &&
9470                        strncmp(message, "    ", 4) == 0) {
9471                 unsigned message_len;
9472
9473                 p = message;
9474                 while (*p && *p == ' ') p++;
9475
9476                 message_len = strlen( p );
9477
9478                 /* [AS] Avoid buffer overflow */
9479                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9480                     strcat(thinkOutput, " ");
9481                     strcat(thinkOutput, p);
9482                 }
9483
9484                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9485                     strcat(programStats.movelist, " ");
9486                     strcat(programStats.movelist, p);
9487                 }
9488
9489                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9490                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9491                     DisplayMove(currentMove - 1);
9492                 }
9493                 return;
9494             }
9495         }
9496         else {
9497             buf1[0] = NULLCHAR;
9498
9499             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9500                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9501             {
9502                 ChessProgramStats cpstats;
9503
9504                 if (plyext != ' ' && plyext != '\t') {
9505                     time *= 100;
9506                 }
9507
9508                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9509                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9510                     curscore = -curscore;
9511                 }
9512
9513                 cpstats.depth = plylev;
9514                 cpstats.nodes = nodes;
9515                 cpstats.time = time;
9516                 cpstats.score = curscore;
9517                 cpstats.got_only_move = 0;
9518                 cpstats.movelist[0] = '\0';
9519
9520                 if (buf1[0] != NULLCHAR) {
9521                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9522                 }
9523
9524                 cpstats.ok_to_send = 0;
9525                 cpstats.line_is_book = 0;
9526                 cpstats.nr_moves = 0;
9527                 cpstats.moves_left = 0;
9528
9529                 SendProgramStatsToFrontend( cps, &cpstats );
9530             }
9531         }
9532     }
9533 }
9534
9535
9536 /* Parse a game score from the character string "game", and
9537    record it as the history of the current game.  The game
9538    score is NOT assumed to start from the standard position.
9539    The display is not updated in any way.
9540    */
9541 void
9542 ParseGameHistory (char *game)
9543 {
9544     ChessMove moveType;
9545     int fromX, fromY, toX, toY, boardIndex;
9546     char promoChar;
9547     char *p, *q;
9548     char buf[MSG_SIZ];
9549
9550     if (appData.debugMode)
9551       fprintf(debugFP, "Parsing game history: %s\n", game);
9552
9553     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9554     gameInfo.site = StrSave(appData.icsHost);
9555     gameInfo.date = PGNDate();
9556     gameInfo.round = StrSave("-");
9557
9558     /* Parse out names of players */
9559     while (*game == ' ') game++;
9560     p = buf;
9561     while (*game != ' ') *p++ = *game++;
9562     *p = NULLCHAR;
9563     gameInfo.white = StrSave(buf);
9564     while (*game == ' ') game++;
9565     p = buf;
9566     while (*game != ' ' && *game != '\n') *p++ = *game++;
9567     *p = NULLCHAR;
9568     gameInfo.black = StrSave(buf);
9569
9570     /* Parse moves */
9571     boardIndex = blackPlaysFirst ? 1 : 0;
9572     yynewstr(game);
9573     for (;;) {
9574         yyboardindex = boardIndex;
9575         moveType = (ChessMove) Myylex();
9576         switch (moveType) {
9577           case IllegalMove:             /* maybe suicide chess, etc. */
9578   if (appData.debugMode) {
9579     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9580     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9581     setbuf(debugFP, NULL);
9582   }
9583           case WhitePromotion:
9584           case BlackPromotion:
9585           case WhiteNonPromotion:
9586           case BlackNonPromotion:
9587           case NormalMove:
9588           case FirstLeg:
9589           case WhiteCapturesEnPassant:
9590           case BlackCapturesEnPassant:
9591           case WhiteKingSideCastle:
9592           case WhiteQueenSideCastle:
9593           case BlackKingSideCastle:
9594           case BlackQueenSideCastle:
9595           case WhiteKingSideCastleWild:
9596           case WhiteQueenSideCastleWild:
9597           case BlackKingSideCastleWild:
9598           case BlackQueenSideCastleWild:
9599           /* PUSH Fabien */
9600           case WhiteHSideCastleFR:
9601           case WhiteASideCastleFR:
9602           case BlackHSideCastleFR:
9603           case BlackASideCastleFR:
9604           /* POP Fabien */
9605             fromX = currentMoveString[0] - AAA;
9606             fromY = currentMoveString[1] - ONE;
9607             toX = currentMoveString[2] - AAA;
9608             toY = currentMoveString[3] - ONE;
9609             promoChar = currentMoveString[4];
9610             break;
9611           case WhiteDrop:
9612           case BlackDrop:
9613             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9614             fromX = moveType == WhiteDrop ?
9615               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9616             (int) CharToPiece(ToLower(currentMoveString[0]));
9617             fromY = DROP_RANK;
9618             toX = currentMoveString[2] - AAA;
9619             toY = currentMoveString[3] - ONE;
9620             promoChar = NULLCHAR;
9621             break;
9622           case AmbiguousMove:
9623             /* bug? */
9624             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9625   if (appData.debugMode) {
9626     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9627     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9628     setbuf(debugFP, NULL);
9629   }
9630             DisplayError(buf, 0);
9631             return;
9632           case ImpossibleMove:
9633             /* bug? */
9634             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9635   if (appData.debugMode) {
9636     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9637     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9638     setbuf(debugFP, NULL);
9639   }
9640             DisplayError(buf, 0);
9641             return;
9642           case EndOfFile:
9643             if (boardIndex < backwardMostMove) {
9644                 /* Oops, gap.  How did that happen? */
9645                 DisplayError(_("Gap in move list"), 0);
9646                 return;
9647             }
9648             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9649             if (boardIndex > forwardMostMove) {
9650                 forwardMostMove = boardIndex;
9651             }
9652             return;
9653           case ElapsedTime:
9654             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9655                 strcat(parseList[boardIndex-1], " ");
9656                 strcat(parseList[boardIndex-1], yy_text);
9657             }
9658             continue;
9659           case Comment:
9660           case PGNTag:
9661           case NAG:
9662           default:
9663             /* ignore */
9664             continue;
9665           case WhiteWins:
9666           case BlackWins:
9667           case GameIsDrawn:
9668           case GameUnfinished:
9669             if (gameMode == IcsExamining) {
9670                 if (boardIndex < backwardMostMove) {
9671                     /* Oops, gap.  How did that happen? */
9672                     return;
9673                 }
9674                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9675                 return;
9676             }
9677             gameInfo.result = moveType;
9678             p = strchr(yy_text, '{');
9679             if (p == NULL) p = strchr(yy_text, '(');
9680             if (p == NULL) {
9681                 p = yy_text;
9682                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9683             } else {
9684                 q = strchr(p, *p == '{' ? '}' : ')');
9685                 if (q != NULL) *q = NULLCHAR;
9686                 p++;
9687             }
9688             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9689             gameInfo.resultDetails = StrSave(p);
9690             continue;
9691         }
9692         if (boardIndex >= forwardMostMove &&
9693             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9694             backwardMostMove = blackPlaysFirst ? 1 : 0;
9695             return;
9696         }
9697         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9698                                  fromY, fromX, toY, toX, promoChar,
9699                                  parseList[boardIndex]);
9700         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9701         /* currentMoveString is set as a side-effect of yylex */
9702         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9703         strcat(moveList[boardIndex], "\n");
9704         boardIndex++;
9705         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9706         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9707           case MT_NONE:
9708           case MT_STALEMATE:
9709           default:
9710             break;
9711           case MT_CHECK:
9712             if(!IS_SHOGI(gameInfo.variant))
9713                 strcat(parseList[boardIndex - 1], "+");
9714             break;
9715           case MT_CHECKMATE:
9716           case MT_STAINMATE:
9717             strcat(parseList[boardIndex - 1], "#");
9718             break;
9719         }
9720     }
9721 }
9722
9723
9724 /* Apply a move to the given board  */
9725 void
9726 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9727 {
9728   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9729   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9730
9731     /* [HGM] compute & store e.p. status and castling rights for new position */
9732     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9733
9734       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9735       oldEP = (signed char)board[EP_STATUS];
9736       board[EP_STATUS] = EP_NONE;
9737
9738   if (fromY == DROP_RANK) {
9739         /* must be first */
9740         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9741             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9742             return;
9743         }
9744         piece = board[toY][toX] = (ChessSquare) fromX;
9745   } else {
9746       ChessSquare victim;
9747       int i;
9748
9749       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9750            victim = board[killY][killX],
9751            board[killY][killX] = EmptySquare,
9752            board[EP_STATUS] = EP_CAPTURE;
9753
9754       if( board[toY][toX] != EmptySquare ) {
9755            board[EP_STATUS] = EP_CAPTURE;
9756            if( (fromX != toX || fromY != toY) && // not igui!
9757                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9758                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9759                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9760            }
9761       }
9762
9763       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9764            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9765                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9766       } else
9767       if( board[fromY][fromX] == WhitePawn ) {
9768            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9769                board[EP_STATUS] = EP_PAWN_MOVE;
9770            if( toY-fromY==2) {
9771                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9772                         gameInfo.variant != VariantBerolina || toX < fromX)
9773                       board[EP_STATUS] = toX | berolina;
9774                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9775                         gameInfo.variant != VariantBerolina || toX > fromX)
9776                       board[EP_STATUS] = toX;
9777            }
9778       } else
9779       if( board[fromY][fromX] == BlackPawn ) {
9780            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9781                board[EP_STATUS] = EP_PAWN_MOVE;
9782            if( toY-fromY== -2) {
9783                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9784                         gameInfo.variant != VariantBerolina || toX < fromX)
9785                       board[EP_STATUS] = toX | berolina;
9786                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9787                         gameInfo.variant != VariantBerolina || toX > fromX)
9788                       board[EP_STATUS] = toX;
9789            }
9790        }
9791
9792        for(i=0; i<nrCastlingRights; i++) {
9793            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9794               board[CASTLING][i] == toX   && castlingRank[i] == toY
9795              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9796        }
9797
9798        if(gameInfo.variant == VariantSChess) { // update virginity
9799            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9800            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9801            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9802            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9803        }
9804
9805      if (fromX == toX && fromY == toY) return;
9806
9807      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9808      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9809      if(gameInfo.variant == VariantKnightmate)
9810          king += (int) WhiteUnicorn - (int) WhiteKing;
9811
9812     /* Code added by Tord: */
9813     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9814     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9815         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9816       board[fromY][fromX] = EmptySquare;
9817       board[toY][toX] = EmptySquare;
9818       if((toX > fromX) != (piece == WhiteRook)) {
9819         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9820       } else {
9821         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9822       }
9823     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9824                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9825       board[fromY][fromX] = EmptySquare;
9826       board[toY][toX] = EmptySquare;
9827       if((toX > fromX) != (piece == BlackRook)) {
9828         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9829       } else {
9830         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9831       }
9832     /* End of code added by Tord */
9833
9834     } else if (board[fromY][fromX] == king
9835         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9836         && toY == fromY && toX > fromX+1) {
9837         board[fromY][fromX] = EmptySquare;
9838         board[toY][toX] = king;
9839         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9840         board[fromY][BOARD_RGHT-1] = EmptySquare;
9841     } else if (board[fromY][fromX] == king
9842         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9843                && toY == fromY && toX < fromX-1) {
9844         board[fromY][fromX] = EmptySquare;
9845         board[toY][toX] = king;
9846         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9847         board[fromY][BOARD_LEFT] = EmptySquare;
9848     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9849                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9850                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9851                ) {
9852         /* white pawn promotion */
9853         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9854         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9855             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9856         board[fromY][fromX] = EmptySquare;
9857     } else if ((fromY >= BOARD_HEIGHT>>1)
9858                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9859                && (toX != fromX)
9860                && gameInfo.variant != VariantXiangqi
9861                && gameInfo.variant != VariantBerolina
9862                && (board[fromY][fromX] == WhitePawn)
9863                && (board[toY][toX] == EmptySquare)) {
9864         board[fromY][fromX] = EmptySquare;
9865         board[toY][toX] = WhitePawn;
9866         captured = board[toY - 1][toX];
9867         board[toY - 1][toX] = EmptySquare;
9868     } else if ((fromY == BOARD_HEIGHT-4)
9869                && (toX == fromX)
9870                && gameInfo.variant == VariantBerolina
9871                && (board[fromY][fromX] == WhitePawn)
9872                && (board[toY][toX] == EmptySquare)) {
9873         board[fromY][fromX] = EmptySquare;
9874         board[toY][toX] = WhitePawn;
9875         if(oldEP & EP_BEROLIN_A) {
9876                 captured = board[fromY][fromX-1];
9877                 board[fromY][fromX-1] = EmptySquare;
9878         }else{  captured = board[fromY][fromX+1];
9879                 board[fromY][fromX+1] = EmptySquare;
9880         }
9881     } else if (board[fromY][fromX] == king
9882         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9883                && toY == fromY && toX > fromX+1) {
9884         board[fromY][fromX] = EmptySquare;
9885         board[toY][toX] = king;
9886         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9887         board[fromY][BOARD_RGHT-1] = EmptySquare;
9888     } else if (board[fromY][fromX] == king
9889         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9890                && toY == fromY && toX < fromX-1) {
9891         board[fromY][fromX] = EmptySquare;
9892         board[toY][toX] = king;
9893         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9894         board[fromY][BOARD_LEFT] = EmptySquare;
9895     } else if (fromY == 7 && fromX == 3
9896                && board[fromY][fromX] == BlackKing
9897                && toY == 7 && toX == 5) {
9898         board[fromY][fromX] = EmptySquare;
9899         board[toY][toX] = BlackKing;
9900         board[fromY][7] = EmptySquare;
9901         board[toY][4] = BlackRook;
9902     } else if (fromY == 7 && fromX == 3
9903                && board[fromY][fromX] == BlackKing
9904                && toY == 7 && toX == 1) {
9905         board[fromY][fromX] = EmptySquare;
9906         board[toY][toX] = BlackKing;
9907         board[fromY][0] = EmptySquare;
9908         board[toY][2] = BlackRook;
9909     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9910                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9911                && toY < promoRank && promoChar
9912                ) {
9913         /* black pawn promotion */
9914         board[toY][toX] = CharToPiece(ToLower(promoChar));
9915         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9916             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9917         board[fromY][fromX] = EmptySquare;
9918     } else if ((fromY < BOARD_HEIGHT>>1)
9919                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9920                && (toX != fromX)
9921                && gameInfo.variant != VariantXiangqi
9922                && gameInfo.variant != VariantBerolina
9923                && (board[fromY][fromX] == BlackPawn)
9924                && (board[toY][toX] == EmptySquare)) {
9925         board[fromY][fromX] = EmptySquare;
9926         board[toY][toX] = BlackPawn;
9927         captured = board[toY + 1][toX];
9928         board[toY + 1][toX] = EmptySquare;
9929     } else if ((fromY == 3)
9930                && (toX == fromX)
9931                && gameInfo.variant == VariantBerolina
9932                && (board[fromY][fromX] == BlackPawn)
9933                && (board[toY][toX] == EmptySquare)) {
9934         board[fromY][fromX] = EmptySquare;
9935         board[toY][toX] = BlackPawn;
9936         if(oldEP & EP_BEROLIN_A) {
9937                 captured = board[fromY][fromX-1];
9938                 board[fromY][fromX-1] = EmptySquare;
9939         }else{  captured = board[fromY][fromX+1];
9940                 board[fromY][fromX+1] = EmptySquare;
9941         }
9942     } else {
9943         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9944         board[fromY][fromX] = EmptySquare;
9945         board[toY][toX] = piece;
9946     }
9947   }
9948
9949     if (gameInfo.holdingsWidth != 0) {
9950
9951       /* !!A lot more code needs to be written to support holdings  */
9952       /* [HGM] OK, so I have written it. Holdings are stored in the */
9953       /* penultimate board files, so they are automaticlly stored   */
9954       /* in the game history.                                       */
9955       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9956                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9957         /* Delete from holdings, by decreasing count */
9958         /* and erasing image if necessary            */
9959         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9960         if(p < (int) BlackPawn) { /* white drop */
9961              p -= (int)WhitePawn;
9962                  p = PieceToNumber((ChessSquare)p);
9963              if(p >= gameInfo.holdingsSize) p = 0;
9964              if(--board[p][BOARD_WIDTH-2] <= 0)
9965                   board[p][BOARD_WIDTH-1] = EmptySquare;
9966              if((int)board[p][BOARD_WIDTH-2] < 0)
9967                         board[p][BOARD_WIDTH-2] = 0;
9968         } else {                  /* black drop */
9969              p -= (int)BlackPawn;
9970                  p = PieceToNumber((ChessSquare)p);
9971              if(p >= gameInfo.holdingsSize) p = 0;
9972              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9973                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9974              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9975                         board[BOARD_HEIGHT-1-p][1] = 0;
9976         }
9977       }
9978       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9979           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9980         /* [HGM] holdings: Add to holdings, if holdings exist */
9981         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9982                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9983                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9984         }
9985         p = (int) captured;
9986         if (p >= (int) BlackPawn) {
9987           p -= (int)BlackPawn;
9988           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9989                   /* in Shogi restore piece to its original  first */
9990                   captured = (ChessSquare) (DEMOTED captured);
9991                   p = DEMOTED p;
9992           }
9993           p = PieceToNumber((ChessSquare)p);
9994           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9995           board[p][BOARD_WIDTH-2]++;
9996           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9997         } else {
9998           p -= (int)WhitePawn;
9999           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10000                   captured = (ChessSquare) (DEMOTED captured);
10001                   p = DEMOTED p;
10002           }
10003           p = PieceToNumber((ChessSquare)p);
10004           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10005           board[BOARD_HEIGHT-1-p][1]++;
10006           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10007         }
10008       }
10009     } else if (gameInfo.variant == VariantAtomic) {
10010       if (captured != EmptySquare) {
10011         int y, x;
10012         for (y = toY-1; y <= toY+1; y++) {
10013           for (x = toX-1; x <= toX+1; x++) {
10014             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10015                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10016               board[y][x] = EmptySquare;
10017             }
10018           }
10019         }
10020         board[toY][toX] = EmptySquare;
10021       }
10022     }
10023
10024     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10025         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10026     } else
10027     if(promoChar == '+') {
10028         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10029         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10030         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10031           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10032     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10033         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10034         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10035            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10036         board[toY][toX] = newPiece;
10037     }
10038     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10039                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10040         // [HGM] superchess: take promotion piece out of holdings
10041         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10042         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10043             if(!--board[k][BOARD_WIDTH-2])
10044                 board[k][BOARD_WIDTH-1] = EmptySquare;
10045         } else {
10046             if(!--board[BOARD_HEIGHT-1-k][1])
10047                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10048         }
10049     }
10050 }
10051
10052 /* Updates forwardMostMove */
10053 void
10054 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10055 {
10056     int x = toX, y = toY;
10057     char *s = parseList[forwardMostMove];
10058     ChessSquare p = boards[forwardMostMove][toY][toX];
10059 //    forwardMostMove++; // [HGM] bare: moved downstream
10060
10061     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10062     (void) CoordsToAlgebraic(boards[forwardMostMove],
10063                              PosFlags(forwardMostMove),
10064                              fromY, fromX, y, x, promoChar,
10065                              s);
10066     if(killX >= 0 && killY >= 0)
10067         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10068
10069     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10070         int timeLeft; static int lastLoadFlag=0; int king, piece;
10071         piece = boards[forwardMostMove][fromY][fromX];
10072         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10073         if(gameInfo.variant == VariantKnightmate)
10074             king += (int) WhiteUnicorn - (int) WhiteKing;
10075         if(forwardMostMove == 0) {
10076             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10077                 fprintf(serverMoves, "%s;", UserName());
10078             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10079                 fprintf(serverMoves, "%s;", second.tidy);
10080             fprintf(serverMoves, "%s;", first.tidy);
10081             if(gameMode == MachinePlaysWhite)
10082                 fprintf(serverMoves, "%s;", UserName());
10083             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10084                 fprintf(serverMoves, "%s;", second.tidy);
10085         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10086         lastLoadFlag = loadFlag;
10087         // print base move
10088         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10089         // print castling suffix
10090         if( toY == fromY && piece == king ) {
10091             if(toX-fromX > 1)
10092                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10093             if(fromX-toX >1)
10094                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10095         }
10096         // e.p. suffix
10097         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10098              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10099              boards[forwardMostMove][toY][toX] == EmptySquare
10100              && fromX != toX && fromY != toY)
10101                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10102         // promotion suffix
10103         if(promoChar != NULLCHAR) {
10104             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10105                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10106                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10107             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10108         }
10109         if(!loadFlag) {
10110                 char buf[MOVE_LEN*2], *p; int len;
10111             fprintf(serverMoves, "/%d/%d",
10112                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10113             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10114             else                      timeLeft = blackTimeRemaining/1000;
10115             fprintf(serverMoves, "/%d", timeLeft);
10116                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10117                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10118                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10119                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10120             fprintf(serverMoves, "/%s", buf);
10121         }
10122         fflush(serverMoves);
10123     }
10124
10125     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10126         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10127       return;
10128     }
10129     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10130     if (commentList[forwardMostMove+1] != NULL) {
10131         free(commentList[forwardMostMove+1]);
10132         commentList[forwardMostMove+1] = NULL;
10133     }
10134     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10135     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10136     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10137     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10138     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10139     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10140     adjustedClock = FALSE;
10141     gameInfo.result = GameUnfinished;
10142     if (gameInfo.resultDetails != NULL) {
10143         free(gameInfo.resultDetails);
10144         gameInfo.resultDetails = NULL;
10145     }
10146     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10147                               moveList[forwardMostMove - 1]);
10148     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10149       case MT_NONE:
10150       case MT_STALEMATE:
10151       default:
10152         break;
10153       case MT_CHECK:
10154         if(!IS_SHOGI(gameInfo.variant))
10155             strcat(parseList[forwardMostMove - 1], "+");
10156         break;
10157       case MT_CHECKMATE:
10158       case MT_STAINMATE:
10159         strcat(parseList[forwardMostMove - 1], "#");
10160         break;
10161     }
10162 }
10163
10164 /* Updates currentMove if not pausing */
10165 void
10166 ShowMove (int fromX, int fromY, int toX, int toY)
10167 {
10168     int instant = (gameMode == PlayFromGameFile) ?
10169         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10170     if(appData.noGUI) return;
10171     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10172         if (!instant) {
10173             if (forwardMostMove == currentMove + 1) {
10174                 AnimateMove(boards[forwardMostMove - 1],
10175                             fromX, fromY, toX, toY);
10176             }
10177         }
10178         currentMove = forwardMostMove;
10179     }
10180
10181     killX = killY = -1; // [HGM] lion: used up
10182
10183     if (instant) return;
10184
10185     DisplayMove(currentMove - 1);
10186     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10187             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10188                 SetHighlights(fromX, fromY, toX, toY);
10189             }
10190     }
10191     DrawPosition(FALSE, boards[currentMove]);
10192     DisplayBothClocks();
10193     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10194 }
10195
10196 void
10197 SendEgtPath (ChessProgramState *cps)
10198 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10199         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10200
10201         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10202
10203         while(*p) {
10204             char c, *q = name+1, *r, *s;
10205
10206             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10207             while(*p && *p != ',') *q++ = *p++;
10208             *q++ = ':'; *q = 0;
10209             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10210                 strcmp(name, ",nalimov:") == 0 ) {
10211                 // take nalimov path from the menu-changeable option first, if it is defined
10212               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10213                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10214             } else
10215             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10216                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10217                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10218                 s = r = StrStr(s, ":") + 1; // beginning of path info
10219                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10220                 c = *r; *r = 0;             // temporarily null-terminate path info
10221                     *--q = 0;               // strip of trailig ':' from name
10222                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10223                 *r = c;
10224                 SendToProgram(buf,cps);     // send egtbpath command for this format
10225             }
10226             if(*p == ',') p++; // read away comma to position for next format name
10227         }
10228 }
10229
10230 static int
10231 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10232 {
10233       int width = 8, height = 8, holdings = 0;             // most common sizes
10234       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10235       // correct the deviations default for each variant
10236       if( v == VariantXiangqi ) width = 9,  height = 10;
10237       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10238       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10239       if( v == VariantCapablanca || v == VariantCapaRandom ||
10240           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10241                                 width = 10;
10242       if( v == VariantCourier ) width = 12;
10243       if( v == VariantSuper )                            holdings = 8;
10244       if( v == VariantGreat )   width = 10,              holdings = 8;
10245       if( v == VariantSChess )                           holdings = 7;
10246       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10247       if( v == VariantChuChess) width = 10, height = 10;
10248       if( v == VariantChu )     width = 12, height = 12;
10249       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10250              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10251              holdingsSize >= 0 && holdingsSize != holdings;
10252 }
10253
10254 char variantError[MSG_SIZ];
10255
10256 char *
10257 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10258 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10259       char *p, *variant = VariantName(v);
10260       static char b[MSG_SIZ];
10261       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10262            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10263                                                holdingsSize, variant); // cook up sized variant name
10264            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10265            if(StrStr(list, b) == NULL) {
10266                // specific sized variant not known, check if general sizing allowed
10267                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10268                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10269                             boardWidth, boardHeight, holdingsSize, engine);
10270                    return NULL;
10271                }
10272                /* [HGM] here we really should compare with the maximum supported board size */
10273            }
10274       } else snprintf(b, MSG_SIZ,"%s", variant);
10275       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10276       p = StrStr(list, b);
10277       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10278       if(p == NULL) {
10279           // occurs not at all in list, or only as sub-string
10280           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10281           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10282               int l = strlen(variantError);
10283               char *q;
10284               while(p != list && p[-1] != ',') p--;
10285               q = strchr(p, ',');
10286               if(q) *q = NULLCHAR;
10287               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10288               if(q) *q= ',';
10289           }
10290           return NULL;
10291       }
10292       return b;
10293 }
10294
10295 void
10296 InitChessProgram (ChessProgramState *cps, int setup)
10297 /* setup needed to setup FRC opening position */
10298 {
10299     char buf[MSG_SIZ], *b;
10300     if (appData.noChessProgram) return;
10301     hintRequested = FALSE;
10302     bookRequested = FALSE;
10303
10304     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10305     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10306     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10307     if(cps->memSize) { /* [HGM] memory */
10308       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10309         SendToProgram(buf, cps);
10310     }
10311     SendEgtPath(cps); /* [HGM] EGT */
10312     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10313       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10314         SendToProgram(buf, cps);
10315     }
10316
10317     SendToProgram(cps->initString, cps);
10318     if (gameInfo.variant != VariantNormal &&
10319         gameInfo.variant != VariantLoadable
10320         /* [HGM] also send variant if board size non-standard */
10321         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10322
10323       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10324                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10325       if (b == NULL) {
10326         DisplayFatalError(variantError, 0, 1);
10327         return;
10328       }
10329
10330       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10331       SendToProgram(buf, cps);
10332     }
10333     currentlyInitializedVariant = gameInfo.variant;
10334
10335     /* [HGM] send opening position in FRC to first engine */
10336     if(setup) {
10337           SendToProgram("force\n", cps);
10338           SendBoard(cps, 0);
10339           /* engine is now in force mode! Set flag to wake it up after first move. */
10340           setboardSpoiledMachineBlack = 1;
10341     }
10342
10343     if (cps->sendICS) {
10344       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10345       SendToProgram(buf, cps);
10346     }
10347     cps->maybeThinking = FALSE;
10348     cps->offeredDraw = 0;
10349     if (!appData.icsActive) {
10350         SendTimeControl(cps, movesPerSession, timeControl,
10351                         timeIncrement, appData.searchDepth,
10352                         searchTime);
10353     }
10354     if (appData.showThinking
10355         // [HGM] thinking: four options require thinking output to be sent
10356         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10357                                 ) {
10358         SendToProgram("post\n", cps);
10359     }
10360     SendToProgram("hard\n", cps);
10361     if (!appData.ponderNextMove) {
10362         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10363            it without being sure what state we are in first.  "hard"
10364            is not a toggle, so that one is OK.
10365          */
10366         SendToProgram("easy\n", cps);
10367     }
10368     if (cps->usePing) {
10369       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10370       SendToProgram(buf, cps);
10371     }
10372     cps->initDone = TRUE;
10373     ClearEngineOutputPane(cps == &second);
10374 }
10375
10376
10377 void
10378 ResendOptions (ChessProgramState *cps)
10379 { // send the stored value of the options
10380   int i;
10381   char buf[MSG_SIZ];
10382   Option *opt = cps->option;
10383   for(i=0; i<cps->nrOptions; i++, opt++) {
10384       switch(opt->type) {
10385         case Spin:
10386         case Slider:
10387         case CheckBox:
10388             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10389           break;
10390         case ComboBox:
10391           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10392           break;
10393         default:
10394             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10395           break;
10396         case Button:
10397         case SaveButton:
10398           continue;
10399       }
10400       SendToProgram(buf, cps);
10401   }
10402 }
10403
10404 void
10405 StartChessProgram (ChessProgramState *cps)
10406 {
10407     char buf[MSG_SIZ];
10408     int err;
10409
10410     if (appData.noChessProgram) return;
10411     cps->initDone = FALSE;
10412
10413     if (strcmp(cps->host, "localhost") == 0) {
10414         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10415     } else if (*appData.remoteShell == NULLCHAR) {
10416         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10417     } else {
10418         if (*appData.remoteUser == NULLCHAR) {
10419           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10420                     cps->program);
10421         } else {
10422           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10423                     cps->host, appData.remoteUser, cps->program);
10424         }
10425         err = StartChildProcess(buf, "", &cps->pr);
10426     }
10427
10428     if (err != 0) {
10429       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10430         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10431         if(cps != &first) return;
10432         appData.noChessProgram = TRUE;
10433         ThawUI();
10434         SetNCPMode();
10435 //      DisplayFatalError(buf, err, 1);
10436 //      cps->pr = NoProc;
10437 //      cps->isr = NULL;
10438         return;
10439     }
10440
10441     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10442     if (cps->protocolVersion > 1) {
10443       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10444       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10445         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10446         cps->comboCnt = 0;  //                and values of combo boxes
10447       }
10448       SendToProgram(buf, cps);
10449       if(cps->reload) ResendOptions(cps);
10450     } else {
10451       SendToProgram("xboard\n", cps);
10452     }
10453 }
10454
10455 void
10456 TwoMachinesEventIfReady P((void))
10457 {
10458   static int curMess = 0;
10459   if (first.lastPing != first.lastPong) {
10460     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10461     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10462     return;
10463   }
10464   if (second.lastPing != second.lastPong) {
10465     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10466     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10467     return;
10468   }
10469   DisplayMessage("", ""); curMess = 0;
10470   TwoMachinesEvent();
10471 }
10472
10473 char *
10474 MakeName (char *template)
10475 {
10476     time_t clock;
10477     struct tm *tm;
10478     static char buf[MSG_SIZ];
10479     char *p = buf;
10480     int i;
10481
10482     clock = time((time_t *)NULL);
10483     tm = localtime(&clock);
10484
10485     while(*p++ = *template++) if(p[-1] == '%') {
10486         switch(*template++) {
10487           case 0:   *p = 0; return buf;
10488           case 'Y': i = tm->tm_year+1900; break;
10489           case 'y': i = tm->tm_year-100; break;
10490           case 'M': i = tm->tm_mon+1; break;
10491           case 'd': i = tm->tm_mday; break;
10492           case 'h': i = tm->tm_hour; break;
10493           case 'm': i = tm->tm_min; break;
10494           case 's': i = tm->tm_sec; break;
10495           default:  i = 0;
10496         }
10497         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10498     }
10499     return buf;
10500 }
10501
10502 int
10503 CountPlayers (char *p)
10504 {
10505     int n = 0;
10506     while(p = strchr(p, '\n')) p++, n++; // count participants
10507     return n;
10508 }
10509
10510 FILE *
10511 WriteTourneyFile (char *results, FILE *f)
10512 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10513     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10514     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10515         // create a file with tournament description
10516         fprintf(f, "-participants {%s}\n", appData.participants);
10517         fprintf(f, "-seedBase %d\n", appData.seedBase);
10518         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10519         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10520         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10521         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10522         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10523         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10524         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10525         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10526         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10527         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10528         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10529         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10530         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10531         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10532         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10533         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10534         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10535         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10536         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10537         fprintf(f, "-smpCores %d\n", appData.smpCores);
10538         if(searchTime > 0)
10539                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10540         else {
10541                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10542                 fprintf(f, "-tc %s\n", appData.timeControl);
10543                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10544         }
10545         fprintf(f, "-results \"%s\"\n", results);
10546     }
10547     return f;
10548 }
10549
10550 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10551
10552 void
10553 Substitute (char *participants, int expunge)
10554 {
10555     int i, changed, changes=0, nPlayers=0;
10556     char *p, *q, *r, buf[MSG_SIZ];
10557     if(participants == NULL) return;
10558     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10559     r = p = participants; q = appData.participants;
10560     while(*p && *p == *q) {
10561         if(*p == '\n') r = p+1, nPlayers++;
10562         p++; q++;
10563     }
10564     if(*p) { // difference
10565         while(*p && *p++ != '\n');
10566         while(*q && *q++ != '\n');
10567       changed = nPlayers;
10568         changes = 1 + (strcmp(p, q) != 0);
10569     }
10570     if(changes == 1) { // a single engine mnemonic was changed
10571         q = r; while(*q) nPlayers += (*q++ == '\n');
10572         p = buf; while(*r && (*p = *r++) != '\n') p++;
10573         *p = NULLCHAR;
10574         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10575         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10576         if(mnemonic[i]) { // The substitute is valid
10577             FILE *f;
10578             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10579                 flock(fileno(f), LOCK_EX);
10580                 ParseArgsFromFile(f);
10581                 fseek(f, 0, SEEK_SET);
10582                 FREE(appData.participants); appData.participants = participants;
10583                 if(expunge) { // erase results of replaced engine
10584                     int len = strlen(appData.results), w, b, dummy;
10585                     for(i=0; i<len; i++) {
10586                         Pairing(i, nPlayers, &w, &b, &dummy);
10587                         if((w == changed || b == changed) && appData.results[i] == '*') {
10588                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10589                             fclose(f);
10590                             return;
10591                         }
10592                     }
10593                     for(i=0; i<len; i++) {
10594                         Pairing(i, nPlayers, &w, &b, &dummy);
10595                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10596                     }
10597                 }
10598                 WriteTourneyFile(appData.results, f);
10599                 fclose(f); // release lock
10600                 return;
10601             }
10602         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10603     }
10604     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10605     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10606     free(participants);
10607     return;
10608 }
10609
10610 int
10611 CheckPlayers (char *participants)
10612 {
10613         int i;
10614         char buf[MSG_SIZ], *p;
10615         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10616         while(p = strchr(participants, '\n')) {
10617             *p = NULLCHAR;
10618             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10619             if(!mnemonic[i]) {
10620                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10621                 *p = '\n';
10622                 DisplayError(buf, 0);
10623                 return 1;
10624             }
10625             *p = '\n';
10626             participants = p + 1;
10627         }
10628         return 0;
10629 }
10630
10631 int
10632 CreateTourney (char *name)
10633 {
10634         FILE *f;
10635         if(matchMode && strcmp(name, appData.tourneyFile)) {
10636              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10637         }
10638         if(name[0] == NULLCHAR) {
10639             if(appData.participants[0])
10640                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10641             return 0;
10642         }
10643         f = fopen(name, "r");
10644         if(f) { // file exists
10645             ASSIGN(appData.tourneyFile, name);
10646             ParseArgsFromFile(f); // parse it
10647         } else {
10648             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10649             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10650                 DisplayError(_("Not enough participants"), 0);
10651                 return 0;
10652             }
10653             if(CheckPlayers(appData.participants)) return 0;
10654             ASSIGN(appData.tourneyFile, name);
10655             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10656             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10657         }
10658         fclose(f);
10659         appData.noChessProgram = FALSE;
10660         appData.clockMode = TRUE;
10661         SetGNUMode();
10662         return 1;
10663 }
10664
10665 int
10666 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10667 {
10668     char buf[MSG_SIZ], *p, *q;
10669     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10670     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10671     skip = !all && group[0]; // if group requested, we start in skip mode
10672     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10673         p = names; q = buf; header = 0;
10674         while(*p && *p != '\n') *q++ = *p++;
10675         *q = 0;
10676         if(*p == '\n') p++;
10677         if(buf[0] == '#') {
10678             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10679             depth++; // we must be entering a new group
10680             if(all) continue; // suppress printing group headers when complete list requested
10681             header = 1;
10682             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10683         }
10684         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10685         if(engineList[i]) free(engineList[i]);
10686         engineList[i] = strdup(buf);
10687         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10688         if(engineMnemonic[i]) free(engineMnemonic[i]);
10689         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10690             strcat(buf, " (");
10691             sscanf(q + 8, "%s", buf + strlen(buf));
10692             strcat(buf, ")");
10693         }
10694         engineMnemonic[i] = strdup(buf);
10695         i++;
10696     }
10697     engineList[i] = engineMnemonic[i] = NULL;
10698     return i;
10699 }
10700
10701 // following implemented as macro to avoid type limitations
10702 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10703
10704 void
10705 SwapEngines (int n)
10706 {   // swap settings for first engine and other engine (so far only some selected options)
10707     int h;
10708     char *p;
10709     if(n == 0) return;
10710     SWAP(directory, p)
10711     SWAP(chessProgram, p)
10712     SWAP(isUCI, h)
10713     SWAP(hasOwnBookUCI, h)
10714     SWAP(protocolVersion, h)
10715     SWAP(reuse, h)
10716     SWAP(scoreIsAbsolute, h)
10717     SWAP(timeOdds, h)
10718     SWAP(logo, p)
10719     SWAP(pgnName, p)
10720     SWAP(pvSAN, h)
10721     SWAP(engOptions, p)
10722     SWAP(engInitString, p)
10723     SWAP(computerString, p)
10724     SWAP(features, p)
10725     SWAP(fenOverride, p)
10726     SWAP(NPS, h)
10727     SWAP(accumulateTC, h)
10728     SWAP(host, p)
10729 }
10730
10731 int
10732 GetEngineLine (char *s, int n)
10733 {
10734     int i;
10735     char buf[MSG_SIZ];
10736     extern char *icsNames;
10737     if(!s || !*s) return 0;
10738     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10739     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10740     if(!mnemonic[i]) return 0;
10741     if(n == 11) return 1; // just testing if there was a match
10742     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10743     if(n == 1) SwapEngines(n);
10744     ParseArgsFromString(buf);
10745     if(n == 1) SwapEngines(n);
10746     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10747         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10748         ParseArgsFromString(buf);
10749     }
10750     return 1;
10751 }
10752
10753 int
10754 SetPlayer (int player, char *p)
10755 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10756     int i;
10757     char buf[MSG_SIZ], *engineName;
10758     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10759     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10760     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10761     if(mnemonic[i]) {
10762         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10763         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10764         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10765         ParseArgsFromString(buf);
10766     } else { // no engine with this nickname is installed!
10767         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10768         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10769         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10770         ModeHighlight();
10771         DisplayError(buf, 0);
10772         return 0;
10773     }
10774     free(engineName);
10775     return i;
10776 }
10777
10778 char *recentEngines;
10779
10780 void
10781 RecentEngineEvent (int nr)
10782 {
10783     int n;
10784 //    SwapEngines(1); // bump first to second
10785 //    ReplaceEngine(&second, 1); // and load it there
10786     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10787     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10788     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10789         ReplaceEngine(&first, 0);
10790         FloatToFront(&appData.recentEngineList, command[n]);
10791     }
10792 }
10793
10794 int
10795 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10796 {   // determine players from game number
10797     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10798
10799     if(appData.tourneyType == 0) {
10800         roundsPerCycle = (nPlayers - 1) | 1;
10801         pairingsPerRound = nPlayers / 2;
10802     } else if(appData.tourneyType > 0) {
10803         roundsPerCycle = nPlayers - appData.tourneyType;
10804         pairingsPerRound = appData.tourneyType;
10805     }
10806     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10807     gamesPerCycle = gamesPerRound * roundsPerCycle;
10808     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10809     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10810     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10811     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10812     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10813     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10814
10815     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10816     if(appData.roundSync) *syncInterval = gamesPerRound;
10817
10818     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10819
10820     if(appData.tourneyType == 0) {
10821         if(curPairing == (nPlayers-1)/2 ) {
10822             *whitePlayer = curRound;
10823             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10824         } else {
10825             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10826             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10827             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10828             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10829         }
10830     } else if(appData.tourneyType > 1) {
10831         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10832         *whitePlayer = curRound + appData.tourneyType;
10833     } else if(appData.tourneyType > 0) {
10834         *whitePlayer = curPairing;
10835         *blackPlayer = curRound + appData.tourneyType;
10836     }
10837
10838     // take care of white/black alternation per round.
10839     // For cycles and games this is already taken care of by default, derived from matchGame!
10840     return curRound & 1;
10841 }
10842
10843 int
10844 NextTourneyGame (int nr, int *swapColors)
10845 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10846     char *p, *q;
10847     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10848     FILE *tf;
10849     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10850     tf = fopen(appData.tourneyFile, "r");
10851     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10852     ParseArgsFromFile(tf); fclose(tf);
10853     InitTimeControls(); // TC might be altered from tourney file
10854
10855     nPlayers = CountPlayers(appData.participants); // count participants
10856     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10857     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10858
10859     if(syncInterval) {
10860         p = q = appData.results;
10861         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10862         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10863             DisplayMessage(_("Waiting for other game(s)"),"");
10864             waitingForGame = TRUE;
10865             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10866             return 0;
10867         }
10868         waitingForGame = FALSE;
10869     }
10870
10871     if(appData.tourneyType < 0) {
10872         if(nr>=0 && !pairingReceived) {
10873             char buf[1<<16];
10874             if(pairing.pr == NoProc) {
10875                 if(!appData.pairingEngine[0]) {
10876                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10877                     return 0;
10878                 }
10879                 StartChessProgram(&pairing); // starts the pairing engine
10880             }
10881             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10882             SendToProgram(buf, &pairing);
10883             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10884             SendToProgram(buf, &pairing);
10885             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10886         }
10887         pairingReceived = 0;                              // ... so we continue here
10888         *swapColors = 0;
10889         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10890         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10891         matchGame = 1; roundNr = nr / syncInterval + 1;
10892     }
10893
10894     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10895
10896     // redefine engines, engine dir, etc.
10897     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10898     if(first.pr == NoProc) {
10899       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10900       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10901     }
10902     if(second.pr == NoProc) {
10903       SwapEngines(1);
10904       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10905       SwapEngines(1);         // and make that valid for second engine by swapping
10906       InitEngine(&second, 1);
10907     }
10908     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10909     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10910     return OK;
10911 }
10912
10913 void
10914 NextMatchGame ()
10915 {   // performs game initialization that does not invoke engines, and then tries to start the game
10916     int res, firstWhite, swapColors = 0;
10917     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10918     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
10919         char buf[MSG_SIZ];
10920         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10921         if(strcmp(buf, currentDebugFile)) { // name has changed
10922             FILE *f = fopen(buf, "w");
10923             if(f) { // if opening the new file failed, just keep using the old one
10924                 ASSIGN(currentDebugFile, buf);
10925                 fclose(debugFP);
10926                 debugFP = f;
10927             }
10928             if(appData.serverFileName) {
10929                 if(serverFP) fclose(serverFP);
10930                 serverFP = fopen(appData.serverFileName, "w");
10931                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10932                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10933             }
10934         }
10935     }
10936     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10937     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10938     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10939     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10940     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10941     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10942     Reset(FALSE, first.pr != NoProc);
10943     res = LoadGameOrPosition(matchGame); // setup game
10944     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10945     if(!res) return; // abort when bad game/pos file
10946     TwoMachinesEvent();
10947 }
10948
10949 void
10950 UserAdjudicationEvent (int result)
10951 {
10952     ChessMove gameResult = GameIsDrawn;
10953
10954     if( result > 0 ) {
10955         gameResult = WhiteWins;
10956     }
10957     else if( result < 0 ) {
10958         gameResult = BlackWins;
10959     }
10960
10961     if( gameMode == TwoMachinesPlay ) {
10962         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10963     }
10964 }
10965
10966
10967 // [HGM] save: calculate checksum of game to make games easily identifiable
10968 int
10969 StringCheckSum (char *s)
10970 {
10971         int i = 0;
10972         if(s==NULL) return 0;
10973         while(*s) i = i*259 + *s++;
10974         return i;
10975 }
10976
10977 int
10978 GameCheckSum ()
10979 {
10980         int i, sum=0;
10981         for(i=backwardMostMove; i<forwardMostMove; i++) {
10982                 sum += pvInfoList[i].depth;
10983                 sum += StringCheckSum(parseList[i]);
10984                 sum += StringCheckSum(commentList[i]);
10985                 sum *= 261;
10986         }
10987         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10988         return sum + StringCheckSum(commentList[i]);
10989 } // end of save patch
10990
10991 void
10992 GameEnds (ChessMove result, char *resultDetails, int whosays)
10993 {
10994     GameMode nextGameMode;
10995     int isIcsGame;
10996     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10997
10998     if(endingGame) return; /* [HGM] crash: forbid recursion */
10999     endingGame = 1;
11000     if(twoBoards) { // [HGM] dual: switch back to one board
11001         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11002         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11003     }
11004     if (appData.debugMode) {
11005       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11006               result, resultDetails ? resultDetails : "(null)", whosays);
11007     }
11008
11009     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11010
11011     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11012
11013     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11014         /* If we are playing on ICS, the server decides when the
11015            game is over, but the engine can offer to draw, claim
11016            a draw, or resign.
11017          */
11018 #if ZIPPY
11019         if (appData.zippyPlay && first.initDone) {
11020             if (result == GameIsDrawn) {
11021                 /* In case draw still needs to be claimed */
11022                 SendToICS(ics_prefix);
11023                 SendToICS("draw\n");
11024             } else if (StrCaseStr(resultDetails, "resign")) {
11025                 SendToICS(ics_prefix);
11026                 SendToICS("resign\n");
11027             }
11028         }
11029 #endif
11030         endingGame = 0; /* [HGM] crash */
11031         return;
11032     }
11033
11034     /* If we're loading the game from a file, stop */
11035     if (whosays == GE_FILE) {
11036       (void) StopLoadGameTimer();
11037       gameFileFP = NULL;
11038     }
11039
11040     /* Cancel draw offers */
11041     first.offeredDraw = second.offeredDraw = 0;
11042
11043     /* If this is an ICS game, only ICS can really say it's done;
11044        if not, anyone can. */
11045     isIcsGame = (gameMode == IcsPlayingWhite ||
11046                  gameMode == IcsPlayingBlack ||
11047                  gameMode == IcsObserving    ||
11048                  gameMode == IcsExamining);
11049
11050     if (!isIcsGame || whosays == GE_ICS) {
11051         /* OK -- not an ICS game, or ICS said it was done */
11052         StopClocks();
11053         if (!isIcsGame && !appData.noChessProgram)
11054           SetUserThinkingEnables();
11055
11056         /* [HGM] if a machine claims the game end we verify this claim */
11057         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11058             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11059                 char claimer;
11060                 ChessMove trueResult = (ChessMove) -1;
11061
11062                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11063                                             first.twoMachinesColor[0] :
11064                                             second.twoMachinesColor[0] ;
11065
11066                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11067                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11068                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11069                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11070                 } else
11071                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11072                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11073                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11074                 } else
11075                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11076                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11077                 }
11078
11079                 // now verify win claims, but not in drop games, as we don't understand those yet
11080                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11081                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11082                     (result == WhiteWins && claimer == 'w' ||
11083                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11084                       if (appData.debugMode) {
11085                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11086                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11087                       }
11088                       if(result != trueResult) {
11089                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11090                               result = claimer == 'w' ? BlackWins : WhiteWins;
11091                               resultDetails = buf;
11092                       }
11093                 } else
11094                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11095                     && (forwardMostMove <= backwardMostMove ||
11096                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11097                         (claimer=='b')==(forwardMostMove&1))
11098                                                                                   ) {
11099                       /* [HGM] verify: draws that were not flagged are false claims */
11100                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11101                       result = claimer == 'w' ? BlackWins : WhiteWins;
11102                       resultDetails = buf;
11103                 }
11104                 /* (Claiming a loss is accepted no questions asked!) */
11105             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11106                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11107                 result = GameUnfinished;
11108                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11109             }
11110             /* [HGM] bare: don't allow bare King to win */
11111             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11112                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11113                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11114                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11115                && result != GameIsDrawn)
11116             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11117                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11118                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11119                         if(p >= 0 && p <= (int)WhiteKing) k++;
11120                 }
11121                 if (appData.debugMode) {
11122                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11123                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11124                 }
11125                 if(k <= 1) {
11126                         result = GameIsDrawn;
11127                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11128                         resultDetails = buf;
11129                 }
11130             }
11131         }
11132
11133
11134         if(serverMoves != NULL && !loadFlag) { char c = '=';
11135             if(result==WhiteWins) c = '+';
11136             if(result==BlackWins) c = '-';
11137             if(resultDetails != NULL)
11138                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11139         }
11140         if (resultDetails != NULL) {
11141             gameInfo.result = result;
11142             gameInfo.resultDetails = StrSave(resultDetails);
11143
11144             /* display last move only if game was not loaded from file */
11145             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11146                 DisplayMove(currentMove - 1);
11147
11148             if (forwardMostMove != 0) {
11149                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11150                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11151                                                                 ) {
11152                     if (*appData.saveGameFile != NULLCHAR) {
11153                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11154                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11155                         else
11156                         SaveGameToFile(appData.saveGameFile, TRUE);
11157                     } else if (appData.autoSaveGames) {
11158                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11159                     }
11160                     if (*appData.savePositionFile != NULLCHAR) {
11161                         SavePositionToFile(appData.savePositionFile);
11162                     }
11163                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11164                 }
11165             }
11166
11167             /* Tell program how game ended in case it is learning */
11168             /* [HGM] Moved this to after saving the PGN, just in case */
11169             /* engine died and we got here through time loss. In that */
11170             /* case we will get a fatal error writing the pipe, which */
11171             /* would otherwise lose us the PGN.                       */
11172             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11173             /* output during GameEnds should never be fatal anymore   */
11174             if (gameMode == MachinePlaysWhite ||
11175                 gameMode == MachinePlaysBlack ||
11176                 gameMode == TwoMachinesPlay ||
11177                 gameMode == IcsPlayingWhite ||
11178                 gameMode == IcsPlayingBlack ||
11179                 gameMode == BeginningOfGame) {
11180                 char buf[MSG_SIZ];
11181                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11182                         resultDetails);
11183                 if (first.pr != NoProc) {
11184                     SendToProgram(buf, &first);
11185                 }
11186                 if (second.pr != NoProc &&
11187                     gameMode == TwoMachinesPlay) {
11188                     SendToProgram(buf, &second);
11189                 }
11190             }
11191         }
11192
11193         if (appData.icsActive) {
11194             if (appData.quietPlay &&
11195                 (gameMode == IcsPlayingWhite ||
11196                  gameMode == IcsPlayingBlack)) {
11197                 SendToICS(ics_prefix);
11198                 SendToICS("set shout 1\n");
11199             }
11200             nextGameMode = IcsIdle;
11201             ics_user_moved = FALSE;
11202             /* clean up premove.  It's ugly when the game has ended and the
11203              * premove highlights are still on the board.
11204              */
11205             if (gotPremove) {
11206               gotPremove = FALSE;
11207               ClearPremoveHighlights();
11208               DrawPosition(FALSE, boards[currentMove]);
11209             }
11210             if (whosays == GE_ICS) {
11211                 switch (result) {
11212                 case WhiteWins:
11213                     if (gameMode == IcsPlayingWhite)
11214                         PlayIcsWinSound();
11215                     else if(gameMode == IcsPlayingBlack)
11216                         PlayIcsLossSound();
11217                     break;
11218                 case BlackWins:
11219                     if (gameMode == IcsPlayingBlack)
11220                         PlayIcsWinSound();
11221                     else if(gameMode == IcsPlayingWhite)
11222                         PlayIcsLossSound();
11223                     break;
11224                 case GameIsDrawn:
11225                     PlayIcsDrawSound();
11226                     break;
11227                 default:
11228                     PlayIcsUnfinishedSound();
11229                 }
11230             }
11231             if(appData.quitNext) { ExitEvent(0); return; }
11232         } else if (gameMode == EditGame ||
11233                    gameMode == PlayFromGameFile ||
11234                    gameMode == AnalyzeMode ||
11235                    gameMode == AnalyzeFile) {
11236             nextGameMode = gameMode;
11237         } else {
11238             nextGameMode = EndOfGame;
11239         }
11240         pausing = FALSE;
11241         ModeHighlight();
11242     } else {
11243         nextGameMode = gameMode;
11244     }
11245
11246     if (appData.noChessProgram) {
11247         gameMode = nextGameMode;
11248         ModeHighlight();
11249         endingGame = 0; /* [HGM] crash */
11250         return;
11251     }
11252
11253     if (first.reuse) {
11254         /* Put first chess program into idle state */
11255         if (first.pr != NoProc &&
11256             (gameMode == MachinePlaysWhite ||
11257              gameMode == MachinePlaysBlack ||
11258              gameMode == TwoMachinesPlay ||
11259              gameMode == IcsPlayingWhite ||
11260              gameMode == IcsPlayingBlack ||
11261              gameMode == BeginningOfGame)) {
11262             SendToProgram("force\n", &first);
11263             if (first.usePing) {
11264               char buf[MSG_SIZ];
11265               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11266               SendToProgram(buf, &first);
11267             }
11268         }
11269     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11270         /* Kill off first chess program */
11271         if (first.isr != NULL)
11272           RemoveInputSource(first.isr);
11273         first.isr = NULL;
11274
11275         if (first.pr != NoProc) {
11276             ExitAnalyzeMode();
11277             DoSleep( appData.delayBeforeQuit );
11278             SendToProgram("quit\n", &first);
11279             DoSleep( appData.delayAfterQuit );
11280             DestroyChildProcess(first.pr, first.useSigterm);
11281             first.reload = TRUE;
11282         }
11283         first.pr = NoProc;
11284     }
11285     if (second.reuse) {
11286         /* Put second chess program into idle state */
11287         if (second.pr != NoProc &&
11288             gameMode == TwoMachinesPlay) {
11289             SendToProgram("force\n", &second);
11290             if (second.usePing) {
11291               char buf[MSG_SIZ];
11292               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11293               SendToProgram(buf, &second);
11294             }
11295         }
11296     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11297         /* Kill off second chess program */
11298         if (second.isr != NULL)
11299           RemoveInputSource(second.isr);
11300         second.isr = NULL;
11301
11302         if (second.pr != NoProc) {
11303             DoSleep( appData.delayBeforeQuit );
11304             SendToProgram("quit\n", &second);
11305             DoSleep( appData.delayAfterQuit );
11306             DestroyChildProcess(second.pr, second.useSigterm);
11307             second.reload = TRUE;
11308         }
11309         second.pr = NoProc;
11310     }
11311
11312     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11313         char resChar = '=';
11314         switch (result) {
11315         case WhiteWins:
11316           resChar = '+';
11317           if (first.twoMachinesColor[0] == 'w') {
11318             first.matchWins++;
11319           } else {
11320             second.matchWins++;
11321           }
11322           break;
11323         case BlackWins:
11324           resChar = '-';
11325           if (first.twoMachinesColor[0] == 'b') {
11326             first.matchWins++;
11327           } else {
11328             second.matchWins++;
11329           }
11330           break;
11331         case GameUnfinished:
11332           resChar = ' ';
11333         default:
11334           break;
11335         }
11336
11337         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11338         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11339             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11340             ReserveGame(nextGame, resChar); // sets nextGame
11341             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11342             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11343         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11344
11345         if (nextGame <= appData.matchGames && !abortMatch) {
11346             gameMode = nextGameMode;
11347             matchGame = nextGame; // this will be overruled in tourney mode!
11348             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11349             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11350             endingGame = 0; /* [HGM] crash */
11351             return;
11352         } else {
11353             gameMode = nextGameMode;
11354             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11355                      first.tidy, second.tidy,
11356                      first.matchWins, second.matchWins,
11357                      appData.matchGames - (first.matchWins + second.matchWins));
11358             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11359             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11360             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11361             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11362                 first.twoMachinesColor = "black\n";
11363                 second.twoMachinesColor = "white\n";
11364             } else {
11365                 first.twoMachinesColor = "white\n";
11366                 second.twoMachinesColor = "black\n";
11367             }
11368         }
11369     }
11370     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11371         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11372       ExitAnalyzeMode();
11373     gameMode = nextGameMode;
11374     ModeHighlight();
11375     endingGame = 0;  /* [HGM] crash */
11376     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11377         if(matchMode == TRUE) { // match through command line: exit with or without popup
11378             if(ranking) {
11379                 ToNrEvent(forwardMostMove);
11380                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11381                 else ExitEvent(0);
11382             } else DisplayFatalError(buf, 0, 0);
11383         } else { // match through menu; just stop, with or without popup
11384             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11385             ModeHighlight();
11386             if(ranking){
11387                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11388             } else DisplayNote(buf);
11389       }
11390       if(ranking) free(ranking);
11391     }
11392 }
11393
11394 /* Assumes program was just initialized (initString sent).
11395    Leaves program in force mode. */
11396 void
11397 FeedMovesToProgram (ChessProgramState *cps, int upto)
11398 {
11399     int i;
11400
11401     if (appData.debugMode)
11402       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11403               startedFromSetupPosition ? "position and " : "",
11404               backwardMostMove, upto, cps->which);
11405     if(currentlyInitializedVariant != gameInfo.variant) {
11406       char buf[MSG_SIZ];
11407         // [HGM] variantswitch: make engine aware of new variant
11408         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11409                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11410                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11411         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11412         SendToProgram(buf, cps);
11413         currentlyInitializedVariant = gameInfo.variant;
11414     }
11415     SendToProgram("force\n", cps);
11416     if (startedFromSetupPosition) {
11417         SendBoard(cps, backwardMostMove);
11418     if (appData.debugMode) {
11419         fprintf(debugFP, "feedMoves\n");
11420     }
11421     }
11422     for (i = backwardMostMove; i < upto; i++) {
11423         SendMoveToProgram(i, cps);
11424     }
11425 }
11426
11427
11428 int
11429 ResurrectChessProgram ()
11430 {
11431      /* The chess program may have exited.
11432         If so, restart it and feed it all the moves made so far. */
11433     static int doInit = 0;
11434
11435     if (appData.noChessProgram) return 1;
11436
11437     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11438         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11439         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11440         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11441     } else {
11442         if (first.pr != NoProc) return 1;
11443         StartChessProgram(&first);
11444     }
11445     InitChessProgram(&first, FALSE);
11446     FeedMovesToProgram(&first, currentMove);
11447
11448     if (!first.sendTime) {
11449         /* can't tell gnuchess what its clock should read,
11450            so we bow to its notion. */
11451         ResetClocks();
11452         timeRemaining[0][currentMove] = whiteTimeRemaining;
11453         timeRemaining[1][currentMove] = blackTimeRemaining;
11454     }
11455
11456     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11457                 appData.icsEngineAnalyze) && first.analysisSupport) {
11458       SendToProgram("analyze\n", &first);
11459       first.analyzing = TRUE;
11460     }
11461     return 1;
11462 }
11463
11464 /*
11465  * Button procedures
11466  */
11467 void
11468 Reset (int redraw, int init)
11469 {
11470     int i;
11471
11472     if (appData.debugMode) {
11473         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11474                 redraw, init, gameMode);
11475     }
11476     CleanupTail(); // [HGM] vari: delete any stored variations
11477     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11478     pausing = pauseExamInvalid = FALSE;
11479     startedFromSetupPosition = blackPlaysFirst = FALSE;
11480     firstMove = TRUE;
11481     whiteFlag = blackFlag = FALSE;
11482     userOfferedDraw = FALSE;
11483     hintRequested = bookRequested = FALSE;
11484     first.maybeThinking = FALSE;
11485     second.maybeThinking = FALSE;
11486     first.bookSuspend = FALSE; // [HGM] book
11487     second.bookSuspend = FALSE;
11488     thinkOutput[0] = NULLCHAR;
11489     lastHint[0] = NULLCHAR;
11490     ClearGameInfo(&gameInfo);
11491     gameInfo.variant = StringToVariant(appData.variant);
11492     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11493     ics_user_moved = ics_clock_paused = FALSE;
11494     ics_getting_history = H_FALSE;
11495     ics_gamenum = -1;
11496     white_holding[0] = black_holding[0] = NULLCHAR;
11497     ClearProgramStats();
11498     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11499
11500     ResetFrontEnd();
11501     ClearHighlights();
11502     flipView = appData.flipView;
11503     ClearPremoveHighlights();
11504     gotPremove = FALSE;
11505     alarmSounded = FALSE;
11506     killX = killY = -1; // [HGM] lion
11507
11508     GameEnds(EndOfFile, NULL, GE_PLAYER);
11509     if(appData.serverMovesName != NULL) {
11510         /* [HGM] prepare to make moves file for broadcasting */
11511         clock_t t = clock();
11512         if(serverMoves != NULL) fclose(serverMoves);
11513         serverMoves = fopen(appData.serverMovesName, "r");
11514         if(serverMoves != NULL) {
11515             fclose(serverMoves);
11516             /* delay 15 sec before overwriting, so all clients can see end */
11517             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11518         }
11519         serverMoves = fopen(appData.serverMovesName, "w");
11520     }
11521
11522     ExitAnalyzeMode();
11523     gameMode = BeginningOfGame;
11524     ModeHighlight();
11525     if(appData.icsActive) gameInfo.variant = VariantNormal;
11526     currentMove = forwardMostMove = backwardMostMove = 0;
11527     MarkTargetSquares(1);
11528     InitPosition(redraw);
11529     for (i = 0; i < MAX_MOVES; i++) {
11530         if (commentList[i] != NULL) {
11531             free(commentList[i]);
11532             commentList[i] = NULL;
11533         }
11534     }
11535     ResetClocks();
11536     timeRemaining[0][0] = whiteTimeRemaining;
11537     timeRemaining[1][0] = blackTimeRemaining;
11538
11539     if (first.pr == NoProc) {
11540         StartChessProgram(&first);
11541     }
11542     if (init) {
11543             InitChessProgram(&first, startedFromSetupPosition);
11544     }
11545     DisplayTitle("");
11546     DisplayMessage("", "");
11547     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11548     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11549     ClearMap();        // [HGM] exclude: invalidate map
11550 }
11551
11552 void
11553 AutoPlayGameLoop ()
11554 {
11555     for (;;) {
11556         if (!AutoPlayOneMove())
11557           return;
11558         if (matchMode || appData.timeDelay == 0)
11559           continue;
11560         if (appData.timeDelay < 0)
11561           return;
11562         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11563         break;
11564     }
11565 }
11566
11567 void
11568 AnalyzeNextGame()
11569 {
11570     ReloadGame(1); // next game
11571 }
11572
11573 int
11574 AutoPlayOneMove ()
11575 {
11576     int fromX, fromY, toX, toY;
11577
11578     if (appData.debugMode) {
11579       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11580     }
11581
11582     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11583       return FALSE;
11584
11585     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11586       pvInfoList[currentMove].depth = programStats.depth;
11587       pvInfoList[currentMove].score = programStats.score;
11588       pvInfoList[currentMove].time  = 0;
11589       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11590       else { // append analysis of final position as comment
11591         char buf[MSG_SIZ];
11592         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11593         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11594       }
11595       programStats.depth = 0;
11596     }
11597
11598     if (currentMove >= forwardMostMove) {
11599       if(gameMode == AnalyzeFile) {
11600           if(appData.loadGameIndex == -1) {
11601             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11602           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11603           } else {
11604           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11605         }
11606       }
11607 //      gameMode = EndOfGame;
11608 //      ModeHighlight();
11609
11610       /* [AS] Clear current move marker at the end of a game */
11611       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11612
11613       return FALSE;
11614     }
11615
11616     toX = moveList[currentMove][2] - AAA;
11617     toY = moveList[currentMove][3] - ONE;
11618
11619     if (moveList[currentMove][1] == '@') {
11620         if (appData.highlightLastMove) {
11621             SetHighlights(-1, -1, toX, toY);
11622         }
11623     } else {
11624         fromX = moveList[currentMove][0] - AAA;
11625         fromY = moveList[currentMove][1] - ONE;
11626
11627         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11628
11629         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11630
11631         if (appData.highlightLastMove) {
11632             SetHighlights(fromX, fromY, toX, toY);
11633         }
11634     }
11635     DisplayMove(currentMove);
11636     SendMoveToProgram(currentMove++, &first);
11637     DisplayBothClocks();
11638     DrawPosition(FALSE, boards[currentMove]);
11639     // [HGM] PV info: always display, routine tests if empty
11640     DisplayComment(currentMove - 1, commentList[currentMove]);
11641     return TRUE;
11642 }
11643
11644
11645 int
11646 LoadGameOneMove (ChessMove readAhead)
11647 {
11648     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11649     char promoChar = NULLCHAR;
11650     ChessMove moveType;
11651     char move[MSG_SIZ];
11652     char *p, *q;
11653
11654     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11655         gameMode != AnalyzeMode && gameMode != Training) {
11656         gameFileFP = NULL;
11657         return FALSE;
11658     }
11659
11660     yyboardindex = forwardMostMove;
11661     if (readAhead != EndOfFile) {
11662       moveType = readAhead;
11663     } else {
11664       if (gameFileFP == NULL)
11665           return FALSE;
11666       moveType = (ChessMove) Myylex();
11667     }
11668
11669     done = FALSE;
11670     switch (moveType) {
11671       case Comment:
11672         if (appData.debugMode)
11673           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11674         p = yy_text;
11675
11676         /* append the comment but don't display it */
11677         AppendComment(currentMove, p, FALSE);
11678         return TRUE;
11679
11680       case WhiteCapturesEnPassant:
11681       case BlackCapturesEnPassant:
11682       case WhitePromotion:
11683       case BlackPromotion:
11684       case WhiteNonPromotion:
11685       case BlackNonPromotion:
11686       case NormalMove:
11687       case FirstLeg:
11688       case WhiteKingSideCastle:
11689       case WhiteQueenSideCastle:
11690       case BlackKingSideCastle:
11691       case BlackQueenSideCastle:
11692       case WhiteKingSideCastleWild:
11693       case WhiteQueenSideCastleWild:
11694       case BlackKingSideCastleWild:
11695       case BlackQueenSideCastleWild:
11696       /* PUSH Fabien */
11697       case WhiteHSideCastleFR:
11698       case WhiteASideCastleFR:
11699       case BlackHSideCastleFR:
11700       case BlackASideCastleFR:
11701       /* POP Fabien */
11702         if (appData.debugMode)
11703           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11704         fromX = currentMoveString[0] - AAA;
11705         fromY = currentMoveString[1] - ONE;
11706         toX = currentMoveString[2] - AAA;
11707         toY = currentMoveString[3] - ONE;
11708         promoChar = currentMoveString[4];
11709         if(promoChar == ';') promoChar = NULLCHAR;
11710         break;
11711
11712       case WhiteDrop:
11713       case BlackDrop:
11714         if (appData.debugMode)
11715           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11716         fromX = moveType == WhiteDrop ?
11717           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11718         (int) CharToPiece(ToLower(currentMoveString[0]));
11719         fromY = DROP_RANK;
11720         toX = currentMoveString[2] - AAA;
11721         toY = currentMoveString[3] - ONE;
11722         break;
11723
11724       case WhiteWins:
11725       case BlackWins:
11726       case GameIsDrawn:
11727       case GameUnfinished:
11728         if (appData.debugMode)
11729           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11730         p = strchr(yy_text, '{');
11731         if (p == NULL) p = strchr(yy_text, '(');
11732         if (p == NULL) {
11733             p = yy_text;
11734             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11735         } else {
11736             q = strchr(p, *p == '{' ? '}' : ')');
11737             if (q != NULL) *q = NULLCHAR;
11738             p++;
11739         }
11740         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11741         GameEnds(moveType, p, GE_FILE);
11742         done = TRUE;
11743         if (cmailMsgLoaded) {
11744             ClearHighlights();
11745             flipView = WhiteOnMove(currentMove);
11746             if (moveType == GameUnfinished) flipView = !flipView;
11747             if (appData.debugMode)
11748               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11749         }
11750         break;
11751
11752       case EndOfFile:
11753         if (appData.debugMode)
11754           fprintf(debugFP, "Parser hit end of file\n");
11755         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11756           case MT_NONE:
11757           case MT_CHECK:
11758             break;
11759           case MT_CHECKMATE:
11760           case MT_STAINMATE:
11761             if (WhiteOnMove(currentMove)) {
11762                 GameEnds(BlackWins, "Black mates", GE_FILE);
11763             } else {
11764                 GameEnds(WhiteWins, "White mates", GE_FILE);
11765             }
11766             break;
11767           case MT_STALEMATE:
11768             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11769             break;
11770         }
11771         done = TRUE;
11772         break;
11773
11774       case MoveNumberOne:
11775         if (lastLoadGameStart == GNUChessGame) {
11776             /* GNUChessGames have numbers, but they aren't move numbers */
11777             if (appData.debugMode)
11778               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11779                       yy_text, (int) moveType);
11780             return LoadGameOneMove(EndOfFile); /* tail recursion */
11781         }
11782         /* else fall thru */
11783
11784       case XBoardGame:
11785       case GNUChessGame:
11786       case PGNTag:
11787         /* Reached start of next game in file */
11788         if (appData.debugMode)
11789           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11790         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11791           case MT_NONE:
11792           case MT_CHECK:
11793             break;
11794           case MT_CHECKMATE:
11795           case MT_STAINMATE:
11796             if (WhiteOnMove(currentMove)) {
11797                 GameEnds(BlackWins, "Black mates", GE_FILE);
11798             } else {
11799                 GameEnds(WhiteWins, "White mates", GE_FILE);
11800             }
11801             break;
11802           case MT_STALEMATE:
11803             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11804             break;
11805         }
11806         done = TRUE;
11807         break;
11808
11809       case PositionDiagram:     /* should not happen; ignore */
11810       case ElapsedTime:         /* ignore */
11811       case NAG:                 /* ignore */
11812         if (appData.debugMode)
11813           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11814                   yy_text, (int) moveType);
11815         return LoadGameOneMove(EndOfFile); /* tail recursion */
11816
11817       case IllegalMove:
11818         if (appData.testLegality) {
11819             if (appData.debugMode)
11820               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11821             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11822                     (forwardMostMove / 2) + 1,
11823                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11824             DisplayError(move, 0);
11825             done = TRUE;
11826         } else {
11827             if (appData.debugMode)
11828               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11829                       yy_text, currentMoveString);
11830             fromX = currentMoveString[0] - AAA;
11831             fromY = currentMoveString[1] - ONE;
11832             toX = currentMoveString[2] - AAA;
11833             toY = currentMoveString[3] - ONE;
11834             promoChar = currentMoveString[4];
11835         }
11836         break;
11837
11838       case AmbiguousMove:
11839         if (appData.debugMode)
11840           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11841         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11842                 (forwardMostMove / 2) + 1,
11843                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11844         DisplayError(move, 0);
11845         done = TRUE;
11846         break;
11847
11848       default:
11849       case ImpossibleMove:
11850         if (appData.debugMode)
11851           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11852         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11853                 (forwardMostMove / 2) + 1,
11854                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11855         DisplayError(move, 0);
11856         done = TRUE;
11857         break;
11858     }
11859
11860     if (done) {
11861         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11862             DrawPosition(FALSE, boards[currentMove]);
11863             DisplayBothClocks();
11864             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11865               DisplayComment(currentMove - 1, commentList[currentMove]);
11866         }
11867         (void) StopLoadGameTimer();
11868         gameFileFP = NULL;
11869         cmailOldMove = forwardMostMove;
11870         return FALSE;
11871     } else {
11872         /* currentMoveString is set as a side-effect of yylex */
11873
11874         thinkOutput[0] = NULLCHAR;
11875         MakeMove(fromX, fromY, toX, toY, promoChar);
11876         killX = killY = -1; // [HGM] lion: used up
11877         currentMove = forwardMostMove;
11878         return TRUE;
11879     }
11880 }
11881
11882 /* Load the nth game from the given file */
11883 int
11884 LoadGameFromFile (char *filename, int n, char *title, int useList)
11885 {
11886     FILE *f;
11887     char buf[MSG_SIZ];
11888
11889     if (strcmp(filename, "-") == 0) {
11890         f = stdin;
11891         title = "stdin";
11892     } else {
11893         f = fopen(filename, "rb");
11894         if (f == NULL) {
11895           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11896             DisplayError(buf, errno);
11897             return FALSE;
11898         }
11899     }
11900     if (fseek(f, 0, 0) == -1) {
11901         /* f is not seekable; probably a pipe */
11902         useList = FALSE;
11903     }
11904     if (useList && n == 0) {
11905         int error = GameListBuild(f);
11906         if (error) {
11907             DisplayError(_("Cannot build game list"), error);
11908         } else if (!ListEmpty(&gameList) &&
11909                    ((ListGame *) gameList.tailPred)->number > 1) {
11910             GameListPopUp(f, title);
11911             return TRUE;
11912         }
11913         GameListDestroy();
11914         n = 1;
11915     }
11916     if (n == 0) n = 1;
11917     return LoadGame(f, n, title, FALSE);
11918 }
11919
11920
11921 void
11922 MakeRegisteredMove ()
11923 {
11924     int fromX, fromY, toX, toY;
11925     char promoChar;
11926     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11927         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11928           case CMAIL_MOVE:
11929           case CMAIL_DRAW:
11930             if (appData.debugMode)
11931               fprintf(debugFP, "Restoring %s for game %d\n",
11932                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11933
11934             thinkOutput[0] = NULLCHAR;
11935             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11936             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11937             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11938             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11939             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11940             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11941             MakeMove(fromX, fromY, toX, toY, promoChar);
11942             ShowMove(fromX, fromY, toX, toY);
11943
11944             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11945               case MT_NONE:
11946               case MT_CHECK:
11947                 break;
11948
11949               case MT_CHECKMATE:
11950               case MT_STAINMATE:
11951                 if (WhiteOnMove(currentMove)) {
11952                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11953                 } else {
11954                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11955                 }
11956                 break;
11957
11958               case MT_STALEMATE:
11959                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11960                 break;
11961             }
11962
11963             break;
11964
11965           case CMAIL_RESIGN:
11966             if (WhiteOnMove(currentMove)) {
11967                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11968             } else {
11969                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11970             }
11971             break;
11972
11973           case CMAIL_ACCEPT:
11974             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11975             break;
11976
11977           default:
11978             break;
11979         }
11980     }
11981
11982     return;
11983 }
11984
11985 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11986 int
11987 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11988 {
11989     int retVal;
11990
11991     if (gameNumber > nCmailGames) {
11992         DisplayError(_("No more games in this message"), 0);
11993         return FALSE;
11994     }
11995     if (f == lastLoadGameFP) {
11996         int offset = gameNumber - lastLoadGameNumber;
11997         if (offset == 0) {
11998             cmailMsg[0] = NULLCHAR;
11999             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12000                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12001                 nCmailMovesRegistered--;
12002             }
12003             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12004             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12005                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12006             }
12007         } else {
12008             if (! RegisterMove()) return FALSE;
12009         }
12010     }
12011
12012     retVal = LoadGame(f, gameNumber, title, useList);
12013
12014     /* Make move registered during previous look at this game, if any */
12015     MakeRegisteredMove();
12016
12017     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12018         commentList[currentMove]
12019           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12020         DisplayComment(currentMove - 1, commentList[currentMove]);
12021     }
12022
12023     return retVal;
12024 }
12025
12026 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12027 int
12028 ReloadGame (int offset)
12029 {
12030     int gameNumber = lastLoadGameNumber + offset;
12031     if (lastLoadGameFP == NULL) {
12032         DisplayError(_("No game has been loaded yet"), 0);
12033         return FALSE;
12034     }
12035     if (gameNumber <= 0) {
12036         DisplayError(_("Can't back up any further"), 0);
12037         return FALSE;
12038     }
12039     if (cmailMsgLoaded) {
12040         return CmailLoadGame(lastLoadGameFP, gameNumber,
12041                              lastLoadGameTitle, lastLoadGameUseList);
12042     } else {
12043         return LoadGame(lastLoadGameFP, gameNumber,
12044                         lastLoadGameTitle, lastLoadGameUseList);
12045     }
12046 }
12047
12048 int keys[EmptySquare+1];
12049
12050 int
12051 PositionMatches (Board b1, Board b2)
12052 {
12053     int r, f, sum=0;
12054     switch(appData.searchMode) {
12055         case 1: return CompareWithRights(b1, b2);
12056         case 2:
12057             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12058                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12059             }
12060             return TRUE;
12061         case 3:
12062             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12063               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12064                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12065             }
12066             return sum==0;
12067         case 4:
12068             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12069                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12070             }
12071             return sum==0;
12072     }
12073     return TRUE;
12074 }
12075
12076 #define Q_PROMO  4
12077 #define Q_EP     3
12078 #define Q_BCASTL 2
12079 #define Q_WCASTL 1
12080
12081 int pieceList[256], quickBoard[256];
12082 ChessSquare pieceType[256] = { EmptySquare };
12083 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12084 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12085 int soughtTotal, turn;
12086 Boolean epOK, flipSearch;
12087
12088 typedef struct {
12089     unsigned char piece, to;
12090 } Move;
12091
12092 #define DSIZE (250000)
12093
12094 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12095 Move *moveDatabase = initialSpace;
12096 unsigned int movePtr, dataSize = DSIZE;
12097
12098 int
12099 MakePieceList (Board board, int *counts)
12100 {
12101     int r, f, n=Q_PROMO, total=0;
12102     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12103     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12104         int sq = f + (r<<4);
12105         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12106             quickBoard[sq] = ++n;
12107             pieceList[n] = sq;
12108             pieceType[n] = board[r][f];
12109             counts[board[r][f]]++;
12110             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12111             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12112             total++;
12113         }
12114     }
12115     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12116     return total;
12117 }
12118
12119 void
12120 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12121 {
12122     int sq = fromX + (fromY<<4);
12123     int piece = quickBoard[sq];
12124     quickBoard[sq] = 0;
12125     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12126     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12127         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12128         moveDatabase[movePtr++].piece = Q_WCASTL;
12129         quickBoard[sq] = piece;
12130         piece = quickBoard[from]; quickBoard[from] = 0;
12131         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12132     } else
12133     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12134         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12135         moveDatabase[movePtr++].piece = Q_BCASTL;
12136         quickBoard[sq] = piece;
12137         piece = quickBoard[from]; quickBoard[from] = 0;
12138         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12139     } else
12140     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12141         quickBoard[(fromY<<4)+toX] = 0;
12142         moveDatabase[movePtr].piece = Q_EP;
12143         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12144         moveDatabase[movePtr].to = sq;
12145     } else
12146     if(promoPiece != pieceType[piece]) {
12147         moveDatabase[movePtr++].piece = Q_PROMO;
12148         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12149     }
12150     moveDatabase[movePtr].piece = piece;
12151     quickBoard[sq] = piece;
12152     movePtr++;
12153 }
12154
12155 int
12156 PackGame (Board board)
12157 {
12158     Move *newSpace = NULL;
12159     moveDatabase[movePtr].piece = 0; // terminate previous game
12160     if(movePtr > dataSize) {
12161         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12162         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12163         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12164         if(newSpace) {
12165             int i;
12166             Move *p = moveDatabase, *q = newSpace;
12167             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12168             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12169             moveDatabase = newSpace;
12170         } else { // calloc failed, we must be out of memory. Too bad...
12171             dataSize = 0; // prevent calloc events for all subsequent games
12172             return 0;     // and signal this one isn't cached
12173         }
12174     }
12175     movePtr++;
12176     MakePieceList(board, counts);
12177     return movePtr;
12178 }
12179
12180 int
12181 QuickCompare (Board board, int *minCounts, int *maxCounts)
12182 {   // compare according to search mode
12183     int r, f;
12184     switch(appData.searchMode)
12185     {
12186       case 1: // exact position match
12187         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12188         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12190         }
12191         break;
12192       case 2: // can have extra material on empty squares
12193         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12194             if(board[r][f] == EmptySquare) continue;
12195             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12196         }
12197         break;
12198       case 3: // material with exact Pawn structure
12199         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12200             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12201             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12202         } // fall through to material comparison
12203       case 4: // exact material
12204         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12205         break;
12206       case 6: // material range with given imbalance
12207         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12208         // fall through to range comparison
12209       case 5: // material range
12210         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12211     }
12212     return TRUE;
12213 }
12214
12215 int
12216 QuickScan (Board board, Move *move)
12217 {   // reconstruct game,and compare all positions in it
12218     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12219     do {
12220         int piece = move->piece;
12221         int to = move->to, from = pieceList[piece];
12222         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12223           if(!piece) return -1;
12224           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12225             piece = (++move)->piece;
12226             from = pieceList[piece];
12227             counts[pieceType[piece]]--;
12228             pieceType[piece] = (ChessSquare) move->to;
12229             counts[move->to]++;
12230           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12231             counts[pieceType[quickBoard[to]]]--;
12232             quickBoard[to] = 0; total--;
12233             move++;
12234             continue;
12235           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12236             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12237             from  = pieceList[piece]; // so this must be King
12238             quickBoard[from] = 0;
12239             pieceList[piece] = to;
12240             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12241             quickBoard[from] = 0; // rook
12242             quickBoard[to] = piece;
12243             to = move->to; piece = move->piece;
12244             goto aftercastle;
12245           }
12246         }
12247         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12248         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12249         quickBoard[from] = 0;
12250       aftercastle:
12251         quickBoard[to] = piece;
12252         pieceList[piece] = to;
12253         cnt++; turn ^= 3;
12254         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12255            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12256            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12257                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12258           ) {
12259             static int lastCounts[EmptySquare+1];
12260             int i;
12261             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12262             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12263         } else stretch = 0;
12264         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12265         move++;
12266     } while(1);
12267 }
12268
12269 void
12270 InitSearch ()
12271 {
12272     int r, f;
12273     flipSearch = FALSE;
12274     CopyBoard(soughtBoard, boards[currentMove]);
12275     soughtTotal = MakePieceList(soughtBoard, maxSought);
12276     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12277     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12278     CopyBoard(reverseBoard, boards[currentMove]);
12279     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12280         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12281         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12282         reverseBoard[r][f] = piece;
12283     }
12284     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12285     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12286     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12287                  || (boards[currentMove][CASTLING][2] == NoRights ||
12288                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12289                  && (boards[currentMove][CASTLING][5] == NoRights ||
12290                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12291       ) {
12292         flipSearch = TRUE;
12293         CopyBoard(flipBoard, soughtBoard);
12294         CopyBoard(rotateBoard, reverseBoard);
12295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12296             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12297             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12298         }
12299     }
12300     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12301     if(appData.searchMode >= 5) {
12302         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12303         MakePieceList(soughtBoard, minSought);
12304         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12305     }
12306     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12307         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12308 }
12309
12310 GameInfo dummyInfo;
12311 static int creatingBook;
12312
12313 int
12314 GameContainsPosition (FILE *f, ListGame *lg)
12315 {
12316     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12317     int fromX, fromY, toX, toY;
12318     char promoChar;
12319     static int initDone=FALSE;
12320
12321     // weed out games based on numerical tag comparison
12322     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12323     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12324     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12325     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12326     if(!initDone) {
12327         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12328         initDone = TRUE;
12329     }
12330     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12331     else CopyBoard(boards[scratch], initialPosition); // default start position
12332     if(lg->moves) {
12333         turn = btm + 1;
12334         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12335         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12336     }
12337     if(btm) plyNr++;
12338     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12339     fseek(f, lg->offset, 0);
12340     yynewfile(f);
12341     while(1) {
12342         yyboardindex = scratch;
12343         quickFlag = plyNr+1;
12344         next = Myylex();
12345         quickFlag = 0;
12346         switch(next) {
12347             case PGNTag:
12348                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12349             default:
12350                 continue;
12351
12352             case XBoardGame:
12353             case GNUChessGame:
12354                 if(plyNr) return -1; // after we have seen moves, this is for new game
12355               continue;
12356
12357             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12358             case ImpossibleMove:
12359             case WhiteWins: // game ends here with these four
12360             case BlackWins:
12361             case GameIsDrawn:
12362             case GameUnfinished:
12363                 return -1;
12364
12365             case IllegalMove:
12366                 if(appData.testLegality) return -1;
12367             case WhiteCapturesEnPassant:
12368             case BlackCapturesEnPassant:
12369             case WhitePromotion:
12370             case BlackPromotion:
12371             case WhiteNonPromotion:
12372             case BlackNonPromotion:
12373             case NormalMove:
12374             case FirstLeg:
12375             case WhiteKingSideCastle:
12376             case WhiteQueenSideCastle:
12377             case BlackKingSideCastle:
12378             case BlackQueenSideCastle:
12379             case WhiteKingSideCastleWild:
12380             case WhiteQueenSideCastleWild:
12381             case BlackKingSideCastleWild:
12382             case BlackQueenSideCastleWild:
12383             case WhiteHSideCastleFR:
12384             case WhiteASideCastleFR:
12385             case BlackHSideCastleFR:
12386             case BlackASideCastleFR:
12387                 fromX = currentMoveString[0] - AAA;
12388                 fromY = currentMoveString[1] - ONE;
12389                 toX = currentMoveString[2] - AAA;
12390                 toY = currentMoveString[3] - ONE;
12391                 promoChar = currentMoveString[4];
12392                 break;
12393             case WhiteDrop:
12394             case BlackDrop:
12395                 fromX = next == WhiteDrop ?
12396                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12397                   (int) CharToPiece(ToLower(currentMoveString[0]));
12398                 fromY = DROP_RANK;
12399                 toX = currentMoveString[2] - AAA;
12400                 toY = currentMoveString[3] - ONE;
12401                 promoChar = 0;
12402                 break;
12403         }
12404         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12405         plyNr++;
12406         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12407         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12408         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12409         if(appData.findMirror) {
12410             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12411             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12412         }
12413     }
12414 }
12415
12416 /* Load the nth game from open file f */
12417 int
12418 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12419 {
12420     ChessMove cm;
12421     char buf[MSG_SIZ];
12422     int gn = gameNumber;
12423     ListGame *lg = NULL;
12424     int numPGNTags = 0;
12425     int err, pos = -1;
12426     GameMode oldGameMode;
12427     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12428
12429     if (appData.debugMode)
12430         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12431
12432     if (gameMode == Training )
12433         SetTrainingModeOff();
12434
12435     oldGameMode = gameMode;
12436     if (gameMode != BeginningOfGame) {
12437       Reset(FALSE, TRUE);
12438     }
12439     killX = killY = -1; // [HGM] lion: in case we did not Reset
12440
12441     gameFileFP = f;
12442     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12443         fclose(lastLoadGameFP);
12444     }
12445
12446     if (useList) {
12447         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12448
12449         if (lg) {
12450             fseek(f, lg->offset, 0);
12451             GameListHighlight(gameNumber);
12452             pos = lg->position;
12453             gn = 1;
12454         }
12455         else {
12456             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12457               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12458             else
12459             DisplayError(_("Game number out of range"), 0);
12460             return FALSE;
12461         }
12462     } else {
12463         GameListDestroy();
12464         if (fseek(f, 0, 0) == -1) {
12465             if (f == lastLoadGameFP ?
12466                 gameNumber == lastLoadGameNumber + 1 :
12467                 gameNumber == 1) {
12468                 gn = 1;
12469             } else {
12470                 DisplayError(_("Can't seek on game file"), 0);
12471                 return FALSE;
12472             }
12473         }
12474     }
12475     lastLoadGameFP = f;
12476     lastLoadGameNumber = gameNumber;
12477     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12478     lastLoadGameUseList = useList;
12479
12480     yynewfile(f);
12481
12482     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12483       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12484                 lg->gameInfo.black);
12485             DisplayTitle(buf);
12486     } else if (*title != NULLCHAR) {
12487         if (gameNumber > 1) {
12488           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12489             DisplayTitle(buf);
12490         } else {
12491             DisplayTitle(title);
12492         }
12493     }
12494
12495     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12496         gameMode = PlayFromGameFile;
12497         ModeHighlight();
12498     }
12499
12500     currentMove = forwardMostMove = backwardMostMove = 0;
12501     CopyBoard(boards[0], initialPosition);
12502     StopClocks();
12503
12504     /*
12505      * Skip the first gn-1 games in the file.
12506      * Also skip over anything that precedes an identifiable
12507      * start of game marker, to avoid being confused by
12508      * garbage at the start of the file.  Currently
12509      * recognized start of game markers are the move number "1",
12510      * the pattern "gnuchess .* game", the pattern
12511      * "^[#;%] [^ ]* game file", and a PGN tag block.
12512      * A game that starts with one of the latter two patterns
12513      * will also have a move number 1, possibly
12514      * following a position diagram.
12515      * 5-4-02: Let's try being more lenient and allowing a game to
12516      * start with an unnumbered move.  Does that break anything?
12517      */
12518     cm = lastLoadGameStart = EndOfFile;
12519     while (gn > 0) {
12520         yyboardindex = forwardMostMove;
12521         cm = (ChessMove) Myylex();
12522         switch (cm) {
12523           case EndOfFile:
12524             if (cmailMsgLoaded) {
12525                 nCmailGames = CMAIL_MAX_GAMES - gn;
12526             } else {
12527                 Reset(TRUE, TRUE);
12528                 DisplayError(_("Game not found in file"), 0);
12529             }
12530             return FALSE;
12531
12532           case GNUChessGame:
12533           case XBoardGame:
12534             gn--;
12535             lastLoadGameStart = cm;
12536             break;
12537
12538           case MoveNumberOne:
12539             switch (lastLoadGameStart) {
12540               case GNUChessGame:
12541               case XBoardGame:
12542               case PGNTag:
12543                 break;
12544               case MoveNumberOne:
12545               case EndOfFile:
12546                 gn--;           /* count this game */
12547                 lastLoadGameStart = cm;
12548                 break;
12549               default:
12550                 /* impossible */
12551                 break;
12552             }
12553             break;
12554
12555           case PGNTag:
12556             switch (lastLoadGameStart) {
12557               case GNUChessGame:
12558               case PGNTag:
12559               case MoveNumberOne:
12560               case EndOfFile:
12561                 gn--;           /* count this game */
12562                 lastLoadGameStart = cm;
12563                 break;
12564               case XBoardGame:
12565                 lastLoadGameStart = cm; /* game counted already */
12566                 break;
12567               default:
12568                 /* impossible */
12569                 break;
12570             }
12571             if (gn > 0) {
12572                 do {
12573                     yyboardindex = forwardMostMove;
12574                     cm = (ChessMove) Myylex();
12575                 } while (cm == PGNTag || cm == Comment);
12576             }
12577             break;
12578
12579           case WhiteWins:
12580           case BlackWins:
12581           case GameIsDrawn:
12582             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12583                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12584                     != CMAIL_OLD_RESULT) {
12585                     nCmailResults ++ ;
12586                     cmailResult[  CMAIL_MAX_GAMES
12587                                 - gn - 1] = CMAIL_OLD_RESULT;
12588                 }
12589             }
12590             break;
12591
12592           case NormalMove:
12593           case FirstLeg:
12594             /* Only a NormalMove can be at the start of a game
12595              * without a position diagram. */
12596             if (lastLoadGameStart == EndOfFile ) {
12597               gn--;
12598               lastLoadGameStart = MoveNumberOne;
12599             }
12600             break;
12601
12602           default:
12603             break;
12604         }
12605     }
12606
12607     if (appData.debugMode)
12608       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12609
12610     if (cm == XBoardGame) {
12611         /* Skip any header junk before position diagram and/or move 1 */
12612         for (;;) {
12613             yyboardindex = forwardMostMove;
12614             cm = (ChessMove) Myylex();
12615
12616             if (cm == EndOfFile ||
12617                 cm == GNUChessGame || cm == XBoardGame) {
12618                 /* Empty game; pretend end-of-file and handle later */
12619                 cm = EndOfFile;
12620                 break;
12621             }
12622
12623             if (cm == MoveNumberOne || cm == PositionDiagram ||
12624                 cm == PGNTag || cm == Comment)
12625               break;
12626         }
12627     } else if (cm == GNUChessGame) {
12628         if (gameInfo.event != NULL) {
12629             free(gameInfo.event);
12630         }
12631         gameInfo.event = StrSave(yy_text);
12632     }
12633
12634     startedFromSetupPosition = FALSE;
12635     while (cm == PGNTag) {
12636         if (appData.debugMode)
12637           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12638         err = ParsePGNTag(yy_text, &gameInfo);
12639         if (!err) numPGNTags++;
12640
12641         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12642         if(gameInfo.variant != oldVariant) {
12643             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12644             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12645             InitPosition(TRUE);
12646             oldVariant = gameInfo.variant;
12647             if (appData.debugMode)
12648               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12649         }
12650
12651
12652         if (gameInfo.fen != NULL) {
12653           Board initial_position;
12654           startedFromSetupPosition = TRUE;
12655           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12656             Reset(TRUE, TRUE);
12657             DisplayError(_("Bad FEN position in file"), 0);
12658             return FALSE;
12659           }
12660           CopyBoard(boards[0], initial_position);
12661           if (blackPlaysFirst) {
12662             currentMove = forwardMostMove = backwardMostMove = 1;
12663             CopyBoard(boards[1], initial_position);
12664             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12665             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12666             timeRemaining[0][1] = whiteTimeRemaining;
12667             timeRemaining[1][1] = blackTimeRemaining;
12668             if (commentList[0] != NULL) {
12669               commentList[1] = commentList[0];
12670               commentList[0] = NULL;
12671             }
12672           } else {
12673             currentMove = forwardMostMove = backwardMostMove = 0;
12674           }
12675           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12676           {   int i;
12677               initialRulePlies = FENrulePlies;
12678               for( i=0; i< nrCastlingRights; i++ )
12679                   initialRights[i] = initial_position[CASTLING][i];
12680           }
12681           yyboardindex = forwardMostMove;
12682           free(gameInfo.fen);
12683           gameInfo.fen = NULL;
12684         }
12685
12686         yyboardindex = forwardMostMove;
12687         cm = (ChessMove) Myylex();
12688
12689         /* Handle comments interspersed among the tags */
12690         while (cm == Comment) {
12691             char *p;
12692             if (appData.debugMode)
12693               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12694             p = yy_text;
12695             AppendComment(currentMove, p, FALSE);
12696             yyboardindex = forwardMostMove;
12697             cm = (ChessMove) Myylex();
12698         }
12699     }
12700
12701     /* don't rely on existence of Event tag since if game was
12702      * pasted from clipboard the Event tag may not exist
12703      */
12704     if (numPGNTags > 0){
12705         char *tags;
12706         if (gameInfo.variant == VariantNormal) {
12707           VariantClass v = StringToVariant(gameInfo.event);
12708           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12709           if(v < VariantShogi) gameInfo.variant = v;
12710         }
12711         if (!matchMode) {
12712           if( appData.autoDisplayTags ) {
12713             tags = PGNTags(&gameInfo);
12714             TagsPopUp(tags, CmailMsg());
12715             free(tags);
12716           }
12717         }
12718     } else {
12719         /* Make something up, but don't display it now */
12720         SetGameInfo();
12721         TagsPopDown();
12722     }
12723
12724     if (cm == PositionDiagram) {
12725         int i, j;
12726         char *p;
12727         Board initial_position;
12728
12729         if (appData.debugMode)
12730           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12731
12732         if (!startedFromSetupPosition) {
12733             p = yy_text;
12734             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12735               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12736                 switch (*p) {
12737                   case '{':
12738                   case '[':
12739                   case '-':
12740                   case ' ':
12741                   case '\t':
12742                   case '\n':
12743                   case '\r':
12744                     break;
12745                   default:
12746                     initial_position[i][j++] = CharToPiece(*p);
12747                     break;
12748                 }
12749             while (*p == ' ' || *p == '\t' ||
12750                    *p == '\n' || *p == '\r') p++;
12751
12752             if (strncmp(p, "black", strlen("black"))==0)
12753               blackPlaysFirst = TRUE;
12754             else
12755               blackPlaysFirst = FALSE;
12756             startedFromSetupPosition = TRUE;
12757
12758             CopyBoard(boards[0], initial_position);
12759             if (blackPlaysFirst) {
12760                 currentMove = forwardMostMove = backwardMostMove = 1;
12761                 CopyBoard(boards[1], initial_position);
12762                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12763                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12764                 timeRemaining[0][1] = whiteTimeRemaining;
12765                 timeRemaining[1][1] = blackTimeRemaining;
12766                 if (commentList[0] != NULL) {
12767                     commentList[1] = commentList[0];
12768                     commentList[0] = NULL;
12769                 }
12770             } else {
12771                 currentMove = forwardMostMove = backwardMostMove = 0;
12772             }
12773         }
12774         yyboardindex = forwardMostMove;
12775         cm = (ChessMove) Myylex();
12776     }
12777
12778   if(!creatingBook) {
12779     if (first.pr == NoProc) {
12780         StartChessProgram(&first);
12781     }
12782     InitChessProgram(&first, FALSE);
12783     SendToProgram("force\n", &first);
12784     if (startedFromSetupPosition) {
12785         SendBoard(&first, forwardMostMove);
12786     if (appData.debugMode) {
12787         fprintf(debugFP, "Load Game\n");
12788     }
12789         DisplayBothClocks();
12790     }
12791   }
12792
12793     /* [HGM] server: flag to write setup moves in broadcast file as one */
12794     loadFlag = appData.suppressLoadMoves;
12795
12796     while (cm == Comment) {
12797         char *p;
12798         if (appData.debugMode)
12799           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12800         p = yy_text;
12801         AppendComment(currentMove, p, FALSE);
12802         yyboardindex = forwardMostMove;
12803         cm = (ChessMove) Myylex();
12804     }
12805
12806     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12807         cm == WhiteWins || cm == BlackWins ||
12808         cm == GameIsDrawn || cm == GameUnfinished) {
12809         DisplayMessage("", _("No moves in game"));
12810         if (cmailMsgLoaded) {
12811             if (appData.debugMode)
12812               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12813             ClearHighlights();
12814             flipView = FALSE;
12815         }
12816         DrawPosition(FALSE, boards[currentMove]);
12817         DisplayBothClocks();
12818         gameMode = EditGame;
12819         ModeHighlight();
12820         gameFileFP = NULL;
12821         cmailOldMove = 0;
12822         return TRUE;
12823     }
12824
12825     // [HGM] PV info: routine tests if comment empty
12826     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12827         DisplayComment(currentMove - 1, commentList[currentMove]);
12828     }
12829     if (!matchMode && appData.timeDelay != 0)
12830       DrawPosition(FALSE, boards[currentMove]);
12831
12832     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12833       programStats.ok_to_send = 1;
12834     }
12835
12836     /* if the first token after the PGN tags is a move
12837      * and not move number 1, retrieve it from the parser
12838      */
12839     if (cm != MoveNumberOne)
12840         LoadGameOneMove(cm);
12841
12842     /* load the remaining moves from the file */
12843     while (LoadGameOneMove(EndOfFile)) {
12844       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12845       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12846     }
12847
12848     /* rewind to the start of the game */
12849     currentMove = backwardMostMove;
12850
12851     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12852
12853     if (oldGameMode == AnalyzeFile) {
12854       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12855       AnalyzeFileEvent();
12856     } else
12857     if (oldGameMode == AnalyzeMode) {
12858       AnalyzeFileEvent();
12859     }
12860
12861     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12862         long int w, b; // [HGM] adjourn: restore saved clock times
12863         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12864         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12865             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12866             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12867         }
12868     }
12869
12870     if(creatingBook) return TRUE;
12871     if (!matchMode && pos > 0) {
12872         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12873     } else
12874     if (matchMode || appData.timeDelay == 0) {
12875       ToEndEvent();
12876     } else if (appData.timeDelay > 0) {
12877       AutoPlayGameLoop();
12878     }
12879
12880     if (appData.debugMode)
12881         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12882
12883     loadFlag = 0; /* [HGM] true game starts */
12884     return TRUE;
12885 }
12886
12887 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12888 int
12889 ReloadPosition (int offset)
12890 {
12891     int positionNumber = lastLoadPositionNumber + offset;
12892     if (lastLoadPositionFP == NULL) {
12893         DisplayError(_("No position has been loaded yet"), 0);
12894         return FALSE;
12895     }
12896     if (positionNumber <= 0) {
12897         DisplayError(_("Can't back up any further"), 0);
12898         return FALSE;
12899     }
12900     return LoadPosition(lastLoadPositionFP, positionNumber,
12901                         lastLoadPositionTitle);
12902 }
12903
12904 /* Load the nth position from the given file */
12905 int
12906 LoadPositionFromFile (char *filename, int n, char *title)
12907 {
12908     FILE *f;
12909     char buf[MSG_SIZ];
12910
12911     if (strcmp(filename, "-") == 0) {
12912         return LoadPosition(stdin, n, "stdin");
12913     } else {
12914         f = fopen(filename, "rb");
12915         if (f == NULL) {
12916             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12917             DisplayError(buf, errno);
12918             return FALSE;
12919         } else {
12920             return LoadPosition(f, n, title);
12921         }
12922     }
12923 }
12924
12925 /* Load the nth position from the given open file, and close it */
12926 int
12927 LoadPosition (FILE *f, int positionNumber, char *title)
12928 {
12929     char *p, line[MSG_SIZ];
12930     Board initial_position;
12931     int i, j, fenMode, pn;
12932
12933     if (gameMode == Training )
12934         SetTrainingModeOff();
12935
12936     if (gameMode != BeginningOfGame) {
12937         Reset(FALSE, TRUE);
12938     }
12939     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12940         fclose(lastLoadPositionFP);
12941     }
12942     if (positionNumber == 0) positionNumber = 1;
12943     lastLoadPositionFP = f;
12944     lastLoadPositionNumber = positionNumber;
12945     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12946     if (first.pr == NoProc && !appData.noChessProgram) {
12947       StartChessProgram(&first);
12948       InitChessProgram(&first, FALSE);
12949     }
12950     pn = positionNumber;
12951     if (positionNumber < 0) {
12952         /* Negative position number means to seek to that byte offset */
12953         if (fseek(f, -positionNumber, 0) == -1) {
12954             DisplayError(_("Can't seek on position file"), 0);
12955             return FALSE;
12956         };
12957         pn = 1;
12958     } else {
12959         if (fseek(f, 0, 0) == -1) {
12960             if (f == lastLoadPositionFP ?
12961                 positionNumber == lastLoadPositionNumber + 1 :
12962                 positionNumber == 1) {
12963                 pn = 1;
12964             } else {
12965                 DisplayError(_("Can't seek on position file"), 0);
12966                 return FALSE;
12967             }
12968         }
12969     }
12970     /* See if this file is FEN or old-style xboard */
12971     if (fgets(line, MSG_SIZ, f) == NULL) {
12972         DisplayError(_("Position not found in file"), 0);
12973         return FALSE;
12974     }
12975     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12976     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12977
12978     if (pn >= 2) {
12979         if (fenMode || line[0] == '#') pn--;
12980         while (pn > 0) {
12981             /* skip positions before number pn */
12982             if (fgets(line, MSG_SIZ, f) == NULL) {
12983                 Reset(TRUE, TRUE);
12984                 DisplayError(_("Position not found in file"), 0);
12985                 return FALSE;
12986             }
12987             if (fenMode || line[0] == '#') pn--;
12988         }
12989     }
12990
12991     if (fenMode) {
12992         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12993             DisplayError(_("Bad FEN position in file"), 0);
12994             return FALSE;
12995         }
12996     } else {
12997         (void) fgets(line, MSG_SIZ, f);
12998         (void) fgets(line, MSG_SIZ, f);
12999
13000         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13001             (void) fgets(line, MSG_SIZ, f);
13002             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13003                 if (*p == ' ')
13004                   continue;
13005                 initial_position[i][j++] = CharToPiece(*p);
13006             }
13007         }
13008
13009         blackPlaysFirst = FALSE;
13010         if (!feof(f)) {
13011             (void) fgets(line, MSG_SIZ, f);
13012             if (strncmp(line, "black", strlen("black"))==0)
13013               blackPlaysFirst = TRUE;
13014         }
13015     }
13016     startedFromSetupPosition = TRUE;
13017
13018     CopyBoard(boards[0], initial_position);
13019     if (blackPlaysFirst) {
13020         currentMove = forwardMostMove = backwardMostMove = 1;
13021         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13022         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13023         CopyBoard(boards[1], initial_position);
13024         DisplayMessage("", _("Black to play"));
13025     } else {
13026         currentMove = forwardMostMove = backwardMostMove = 0;
13027         DisplayMessage("", _("White to play"));
13028     }
13029     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13030     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13031         SendToProgram("force\n", &first);
13032         SendBoard(&first, forwardMostMove);
13033     }
13034     if (appData.debugMode) {
13035 int i, j;
13036   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13037   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13038         fprintf(debugFP, "Load Position\n");
13039     }
13040
13041     if (positionNumber > 1) {
13042       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13043         DisplayTitle(line);
13044     } else {
13045         DisplayTitle(title);
13046     }
13047     gameMode = EditGame;
13048     ModeHighlight();
13049     ResetClocks();
13050     timeRemaining[0][1] = whiteTimeRemaining;
13051     timeRemaining[1][1] = blackTimeRemaining;
13052     DrawPosition(FALSE, boards[currentMove]);
13053
13054     return TRUE;
13055 }
13056
13057
13058 void
13059 CopyPlayerNameIntoFileName (char **dest, char *src)
13060 {
13061     while (*src != NULLCHAR && *src != ',') {
13062         if (*src == ' ') {
13063             *(*dest)++ = '_';
13064             src++;
13065         } else {
13066             *(*dest)++ = *src++;
13067         }
13068     }
13069 }
13070
13071 char *
13072 DefaultFileName (char *ext)
13073 {
13074     static char def[MSG_SIZ];
13075     char *p;
13076
13077     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13078         p = def;
13079         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13080         *p++ = '-';
13081         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13082         *p++ = '.';
13083         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13084     } else {
13085         def[0] = NULLCHAR;
13086     }
13087     return def;
13088 }
13089
13090 /* Save the current game to the given file */
13091 int
13092 SaveGameToFile (char *filename, int append)
13093 {
13094     FILE *f;
13095     char buf[MSG_SIZ];
13096     int result, i, t,tot=0;
13097
13098     if (strcmp(filename, "-") == 0) {
13099         return SaveGame(stdout, 0, NULL);
13100     } else {
13101         for(i=0; i<10; i++) { // upto 10 tries
13102              f = fopen(filename, append ? "a" : "w");
13103              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13104              if(f || errno != 13) break;
13105              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13106              tot += t;
13107         }
13108         if (f == NULL) {
13109             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13110             DisplayError(buf, errno);
13111             return FALSE;
13112         } else {
13113             safeStrCpy(buf, lastMsg, MSG_SIZ);
13114             DisplayMessage(_("Waiting for access to save file"), "");
13115             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13116             DisplayMessage(_("Saving game"), "");
13117             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13118             result = SaveGame(f, 0, NULL);
13119             DisplayMessage(buf, "");
13120             return result;
13121         }
13122     }
13123 }
13124
13125 char *
13126 SavePart (char *str)
13127 {
13128     static char buf[MSG_SIZ];
13129     char *p;
13130
13131     p = strchr(str, ' ');
13132     if (p == NULL) return str;
13133     strncpy(buf, str, p - str);
13134     buf[p - str] = NULLCHAR;
13135     return buf;
13136 }
13137
13138 #define PGN_MAX_LINE 75
13139
13140 #define PGN_SIDE_WHITE  0
13141 #define PGN_SIDE_BLACK  1
13142
13143 static int
13144 FindFirstMoveOutOfBook (int side)
13145 {
13146     int result = -1;
13147
13148     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13149         int index = backwardMostMove;
13150         int has_book_hit = 0;
13151
13152         if( (index % 2) != side ) {
13153             index++;
13154         }
13155
13156         while( index < forwardMostMove ) {
13157             /* Check to see if engine is in book */
13158             int depth = pvInfoList[index].depth;
13159             int score = pvInfoList[index].score;
13160             int in_book = 0;
13161
13162             if( depth <= 2 ) {
13163                 in_book = 1;
13164             }
13165             else if( score == 0 && depth == 63 ) {
13166                 in_book = 1; /* Zappa */
13167             }
13168             else if( score == 2 && depth == 99 ) {
13169                 in_book = 1; /* Abrok */
13170             }
13171
13172             has_book_hit += in_book;
13173
13174             if( ! in_book ) {
13175                 result = index;
13176
13177                 break;
13178             }
13179
13180             index += 2;
13181         }
13182     }
13183
13184     return result;
13185 }
13186
13187 void
13188 GetOutOfBookInfo (char * buf)
13189 {
13190     int oob[2];
13191     int i;
13192     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13193
13194     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13195     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13196
13197     *buf = '\0';
13198
13199     if( oob[0] >= 0 || oob[1] >= 0 ) {
13200         for( i=0; i<2; i++ ) {
13201             int idx = oob[i];
13202
13203             if( idx >= 0 ) {
13204                 if( i > 0 && oob[0] >= 0 ) {
13205                     strcat( buf, "   " );
13206                 }
13207
13208                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13209                 sprintf( buf+strlen(buf), "%s%.2f",
13210                     pvInfoList[idx].score >= 0 ? "+" : "",
13211                     pvInfoList[idx].score / 100.0 );
13212             }
13213         }
13214     }
13215 }
13216
13217 /* Save game in PGN style and close the file */
13218 int
13219 SaveGamePGN (FILE *f)
13220 {
13221     int i, offset, linelen, newblock;
13222 //    char *movetext;
13223     char numtext[32];
13224     int movelen, numlen, blank;
13225     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13226
13227     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13228
13229     PrintPGNTags(f, &gameInfo);
13230
13231     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13232
13233     if (backwardMostMove > 0 || startedFromSetupPosition) {
13234         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13235         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13236         fprintf(f, "\n{--------------\n");
13237         PrintPosition(f, backwardMostMove);
13238         fprintf(f, "--------------}\n");
13239         free(fen);
13240     }
13241     else {
13242         /* [AS] Out of book annotation */
13243         if( appData.saveOutOfBookInfo ) {
13244             char buf[64];
13245
13246             GetOutOfBookInfo( buf );
13247
13248             if( buf[0] != '\0' ) {
13249                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13250             }
13251         }
13252
13253         fprintf(f, "\n");
13254     }
13255
13256     i = backwardMostMove;
13257     linelen = 0;
13258     newblock = TRUE;
13259
13260     while (i < forwardMostMove) {
13261         /* Print comments preceding this move */
13262         if (commentList[i] != NULL) {
13263             if (linelen > 0) fprintf(f, "\n");
13264             fprintf(f, "%s", commentList[i]);
13265             linelen = 0;
13266             newblock = TRUE;
13267         }
13268
13269         /* Format move number */
13270         if ((i % 2) == 0)
13271           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13272         else
13273           if (newblock)
13274             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13275           else
13276             numtext[0] = NULLCHAR;
13277
13278         numlen = strlen(numtext);
13279         newblock = FALSE;
13280
13281         /* Print move number */
13282         blank = linelen > 0 && numlen > 0;
13283         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13284             fprintf(f, "\n");
13285             linelen = 0;
13286             blank = 0;
13287         }
13288         if (blank) {
13289             fprintf(f, " ");
13290             linelen++;
13291         }
13292         fprintf(f, "%s", numtext);
13293         linelen += numlen;
13294
13295         /* Get move */
13296         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13297         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13298
13299         /* Print move */
13300         blank = linelen > 0 && movelen > 0;
13301         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13302             fprintf(f, "\n");
13303             linelen = 0;
13304             blank = 0;
13305         }
13306         if (blank) {
13307             fprintf(f, " ");
13308             linelen++;
13309         }
13310         fprintf(f, "%s", move_buffer);
13311         linelen += movelen;
13312
13313         /* [AS] Add PV info if present */
13314         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13315             /* [HGM] add time */
13316             char buf[MSG_SIZ]; int seconds;
13317
13318             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13319
13320             if( seconds <= 0)
13321               buf[0] = 0;
13322             else
13323               if( seconds < 30 )
13324                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13325               else
13326                 {
13327                   seconds = (seconds + 4)/10; // round to full seconds
13328                   if( seconds < 60 )
13329                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13330                   else
13331                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13332                 }
13333
13334             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13335                       pvInfoList[i].score >= 0 ? "+" : "",
13336                       pvInfoList[i].score / 100.0,
13337                       pvInfoList[i].depth,
13338                       buf );
13339
13340             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13341
13342             /* Print score/depth */
13343             blank = linelen > 0 && movelen > 0;
13344             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13345                 fprintf(f, "\n");
13346                 linelen = 0;
13347                 blank = 0;
13348             }
13349             if (blank) {
13350                 fprintf(f, " ");
13351                 linelen++;
13352             }
13353             fprintf(f, "%s", move_buffer);
13354             linelen += movelen;
13355         }
13356
13357         i++;
13358     }
13359
13360     /* Start a new line */
13361     if (linelen > 0) fprintf(f, "\n");
13362
13363     /* Print comments after last move */
13364     if (commentList[i] != NULL) {
13365         fprintf(f, "%s\n", commentList[i]);
13366     }
13367
13368     /* Print result */
13369     if (gameInfo.resultDetails != NULL &&
13370         gameInfo.resultDetails[0] != NULLCHAR) {
13371         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13372         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13373            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13374             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13375         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13376     } else {
13377         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13378     }
13379
13380     fclose(f);
13381     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13382     return TRUE;
13383 }
13384
13385 /* Save game in old style and close the file */
13386 int
13387 SaveGameOldStyle (FILE *f)
13388 {
13389     int i, offset;
13390     time_t tm;
13391
13392     tm = time((time_t *) NULL);
13393
13394     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13395     PrintOpponents(f);
13396
13397     if (backwardMostMove > 0 || startedFromSetupPosition) {
13398         fprintf(f, "\n[--------------\n");
13399         PrintPosition(f, backwardMostMove);
13400         fprintf(f, "--------------]\n");
13401     } else {
13402         fprintf(f, "\n");
13403     }
13404
13405     i = backwardMostMove;
13406     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13407
13408     while (i < forwardMostMove) {
13409         if (commentList[i] != NULL) {
13410             fprintf(f, "[%s]\n", commentList[i]);
13411         }
13412
13413         if ((i % 2) == 1) {
13414             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13415             i++;
13416         } else {
13417             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13418             i++;
13419             if (commentList[i] != NULL) {
13420                 fprintf(f, "\n");
13421                 continue;
13422             }
13423             if (i >= forwardMostMove) {
13424                 fprintf(f, "\n");
13425                 break;
13426             }
13427             fprintf(f, "%s\n", parseList[i]);
13428             i++;
13429         }
13430     }
13431
13432     if (commentList[i] != NULL) {
13433         fprintf(f, "[%s]\n", commentList[i]);
13434     }
13435
13436     /* This isn't really the old style, but it's close enough */
13437     if (gameInfo.resultDetails != NULL &&
13438         gameInfo.resultDetails[0] != NULLCHAR) {
13439         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13440                 gameInfo.resultDetails);
13441     } else {
13442         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13443     }
13444
13445     fclose(f);
13446     return TRUE;
13447 }
13448
13449 /* Save the current game to open file f and close the file */
13450 int
13451 SaveGame (FILE *f, int dummy, char *dummy2)
13452 {
13453     if (gameMode == EditPosition) EditPositionDone(TRUE);
13454     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13455     if (appData.oldSaveStyle)
13456       return SaveGameOldStyle(f);
13457     else
13458       return SaveGamePGN(f);
13459 }
13460
13461 /* Save the current position to the given file */
13462 int
13463 SavePositionToFile (char *filename)
13464 {
13465     FILE *f;
13466     char buf[MSG_SIZ];
13467
13468     if (strcmp(filename, "-") == 0) {
13469         return SavePosition(stdout, 0, NULL);
13470     } else {
13471         f = fopen(filename, "a");
13472         if (f == NULL) {
13473             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13474             DisplayError(buf, errno);
13475             return FALSE;
13476         } else {
13477             safeStrCpy(buf, lastMsg, MSG_SIZ);
13478             DisplayMessage(_("Waiting for access to save file"), "");
13479             flock(fileno(f), LOCK_EX); // [HGM] lock
13480             DisplayMessage(_("Saving position"), "");
13481             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13482             SavePosition(f, 0, NULL);
13483             DisplayMessage(buf, "");
13484             return TRUE;
13485         }
13486     }
13487 }
13488
13489 /* Save the current position to the given open file and close the file */
13490 int
13491 SavePosition (FILE *f, int dummy, char *dummy2)
13492 {
13493     time_t tm;
13494     char *fen;
13495
13496     if (gameMode == EditPosition) EditPositionDone(TRUE);
13497     if (appData.oldSaveStyle) {
13498         tm = time((time_t *) NULL);
13499
13500         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13501         PrintOpponents(f);
13502         fprintf(f, "[--------------\n");
13503         PrintPosition(f, currentMove);
13504         fprintf(f, "--------------]\n");
13505     } else {
13506         fen = PositionToFEN(currentMove, NULL, 1);
13507         fprintf(f, "%s\n", fen);
13508         free(fen);
13509     }
13510     fclose(f);
13511     return TRUE;
13512 }
13513
13514 void
13515 ReloadCmailMsgEvent (int unregister)
13516 {
13517 #if !WIN32
13518     static char *inFilename = NULL;
13519     static char *outFilename;
13520     int i;
13521     struct stat inbuf, outbuf;
13522     int status;
13523
13524     /* Any registered moves are unregistered if unregister is set, */
13525     /* i.e. invoked by the signal handler */
13526     if (unregister) {
13527         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13528             cmailMoveRegistered[i] = FALSE;
13529             if (cmailCommentList[i] != NULL) {
13530                 free(cmailCommentList[i]);
13531                 cmailCommentList[i] = NULL;
13532             }
13533         }
13534         nCmailMovesRegistered = 0;
13535     }
13536
13537     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13538         cmailResult[i] = CMAIL_NOT_RESULT;
13539     }
13540     nCmailResults = 0;
13541
13542     if (inFilename == NULL) {
13543         /* Because the filenames are static they only get malloced once  */
13544         /* and they never get freed                                      */
13545         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13546         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13547
13548         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13549         sprintf(outFilename, "%s.out", appData.cmailGameName);
13550     }
13551
13552     status = stat(outFilename, &outbuf);
13553     if (status < 0) {
13554         cmailMailedMove = FALSE;
13555     } else {
13556         status = stat(inFilename, &inbuf);
13557         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13558     }
13559
13560     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13561        counts the games, notes how each one terminated, etc.
13562
13563        It would be nice to remove this kludge and instead gather all
13564        the information while building the game list.  (And to keep it
13565        in the game list nodes instead of having a bunch of fixed-size
13566        parallel arrays.)  Note this will require getting each game's
13567        termination from the PGN tags, as the game list builder does
13568        not process the game moves.  --mann
13569        */
13570     cmailMsgLoaded = TRUE;
13571     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13572
13573     /* Load first game in the file or popup game menu */
13574     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13575
13576 #endif /* !WIN32 */
13577     return;
13578 }
13579
13580 int
13581 RegisterMove ()
13582 {
13583     FILE *f;
13584     char string[MSG_SIZ];
13585
13586     if (   cmailMailedMove
13587         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13588         return TRUE;            /* Allow free viewing  */
13589     }
13590
13591     /* Unregister move to ensure that we don't leave RegisterMove        */
13592     /* with the move registered when the conditions for registering no   */
13593     /* longer hold                                                       */
13594     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13595         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13596         nCmailMovesRegistered --;
13597
13598         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13599           {
13600               free(cmailCommentList[lastLoadGameNumber - 1]);
13601               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13602           }
13603     }
13604
13605     if (cmailOldMove == -1) {
13606         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13607         return FALSE;
13608     }
13609
13610     if (currentMove > cmailOldMove + 1) {
13611         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13612         return FALSE;
13613     }
13614
13615     if (currentMove < cmailOldMove) {
13616         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13617         return FALSE;
13618     }
13619
13620     if (forwardMostMove > currentMove) {
13621         /* Silently truncate extra moves */
13622         TruncateGame();
13623     }
13624
13625     if (   (currentMove == cmailOldMove + 1)
13626         || (   (currentMove == cmailOldMove)
13627             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13628                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13629         if (gameInfo.result != GameUnfinished) {
13630             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13631         }
13632
13633         if (commentList[currentMove] != NULL) {
13634             cmailCommentList[lastLoadGameNumber - 1]
13635               = StrSave(commentList[currentMove]);
13636         }
13637         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13638
13639         if (appData.debugMode)
13640           fprintf(debugFP, "Saving %s for game %d\n",
13641                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13642
13643         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13644
13645         f = fopen(string, "w");
13646         if (appData.oldSaveStyle) {
13647             SaveGameOldStyle(f); /* also closes the file */
13648
13649             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13650             f = fopen(string, "w");
13651             SavePosition(f, 0, NULL); /* also closes the file */
13652         } else {
13653             fprintf(f, "{--------------\n");
13654             PrintPosition(f, currentMove);
13655             fprintf(f, "--------------}\n\n");
13656
13657             SaveGame(f, 0, NULL); /* also closes the file*/
13658         }
13659
13660         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13661         nCmailMovesRegistered ++;
13662     } else if (nCmailGames == 1) {
13663         DisplayError(_("You have not made a move yet"), 0);
13664         return FALSE;
13665     }
13666
13667     return TRUE;
13668 }
13669
13670 void
13671 MailMoveEvent ()
13672 {
13673 #if !WIN32
13674     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13675     FILE *commandOutput;
13676     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13677     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13678     int nBuffers;
13679     int i;
13680     int archived;
13681     char *arcDir;
13682
13683     if (! cmailMsgLoaded) {
13684         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13685         return;
13686     }
13687
13688     if (nCmailGames == nCmailResults) {
13689         DisplayError(_("No unfinished games"), 0);
13690         return;
13691     }
13692
13693 #if CMAIL_PROHIBIT_REMAIL
13694     if (cmailMailedMove) {
13695       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);
13696         DisplayError(msg, 0);
13697         return;
13698     }
13699 #endif
13700
13701     if (! (cmailMailedMove || RegisterMove())) return;
13702
13703     if (   cmailMailedMove
13704         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13705       snprintf(string, MSG_SIZ, partCommandString,
13706                appData.debugMode ? " -v" : "", appData.cmailGameName);
13707         commandOutput = popen(string, "r");
13708
13709         if (commandOutput == NULL) {
13710             DisplayError(_("Failed to invoke cmail"), 0);
13711         } else {
13712             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13713                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13714             }
13715             if (nBuffers > 1) {
13716                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13717                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13718                 nBytes = MSG_SIZ - 1;
13719             } else {
13720                 (void) memcpy(msg, buffer, nBytes);
13721             }
13722             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13723
13724             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13725                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13726
13727                 archived = TRUE;
13728                 for (i = 0; i < nCmailGames; i ++) {
13729                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13730                         archived = FALSE;
13731                     }
13732                 }
13733                 if (   archived
13734                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13735                         != NULL)) {
13736                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13737                            arcDir,
13738                            appData.cmailGameName,
13739                            gameInfo.date);
13740                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13741                     cmailMsgLoaded = FALSE;
13742                 }
13743             }
13744
13745             DisplayInformation(msg);
13746             pclose(commandOutput);
13747         }
13748     } else {
13749         if ((*cmailMsg) != '\0') {
13750             DisplayInformation(cmailMsg);
13751         }
13752     }
13753
13754     return;
13755 #endif /* !WIN32 */
13756 }
13757
13758 char *
13759 CmailMsg ()
13760 {
13761 #if WIN32
13762     return NULL;
13763 #else
13764     int  prependComma = 0;
13765     char number[5];
13766     char string[MSG_SIZ];       /* Space for game-list */
13767     int  i;
13768
13769     if (!cmailMsgLoaded) return "";
13770
13771     if (cmailMailedMove) {
13772       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13773     } else {
13774         /* Create a list of games left */
13775       snprintf(string, MSG_SIZ, "[");
13776         for (i = 0; i < nCmailGames; i ++) {
13777             if (! (   cmailMoveRegistered[i]
13778                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13779                 if (prependComma) {
13780                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13781                 } else {
13782                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13783                     prependComma = 1;
13784                 }
13785
13786                 strcat(string, number);
13787             }
13788         }
13789         strcat(string, "]");
13790
13791         if (nCmailMovesRegistered + nCmailResults == 0) {
13792             switch (nCmailGames) {
13793               case 1:
13794                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13795                 break;
13796
13797               case 2:
13798                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13799                 break;
13800
13801               default:
13802                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13803                          nCmailGames);
13804                 break;
13805             }
13806         } else {
13807             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13808               case 1:
13809                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13810                          string);
13811                 break;
13812
13813               case 0:
13814                 if (nCmailResults == nCmailGames) {
13815                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13816                 } else {
13817                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13818                 }
13819                 break;
13820
13821               default:
13822                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13823                          string);
13824             }
13825         }
13826     }
13827     return cmailMsg;
13828 #endif /* WIN32 */
13829 }
13830
13831 void
13832 ResetGameEvent ()
13833 {
13834     if (gameMode == Training)
13835       SetTrainingModeOff();
13836
13837     Reset(TRUE, TRUE);
13838     cmailMsgLoaded = FALSE;
13839     if (appData.icsActive) {
13840       SendToICS(ics_prefix);
13841       SendToICS("refresh\n");
13842     }
13843 }
13844
13845 void
13846 ExitEvent (int status)
13847 {
13848     exiting++;
13849     if (exiting > 2) {
13850       /* Give up on clean exit */
13851       exit(status);
13852     }
13853     if (exiting > 1) {
13854       /* Keep trying for clean exit */
13855       return;
13856     }
13857
13858     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13859
13860     if (telnetISR != NULL) {
13861       RemoveInputSource(telnetISR);
13862     }
13863     if (icsPR != NoProc) {
13864       DestroyChildProcess(icsPR, TRUE);
13865     }
13866
13867     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13868     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13869
13870     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13871     /* make sure this other one finishes before killing it!                  */
13872     if(endingGame) { int count = 0;
13873         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13874         while(endingGame && count++ < 10) DoSleep(1);
13875         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13876     }
13877
13878     /* Kill off chess programs */
13879     if (first.pr != NoProc) {
13880         ExitAnalyzeMode();
13881
13882         DoSleep( appData.delayBeforeQuit );
13883         SendToProgram("quit\n", &first);
13884         DoSleep( appData.delayAfterQuit );
13885         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13886     }
13887     if (second.pr != NoProc) {
13888         DoSleep( appData.delayBeforeQuit );
13889         SendToProgram("quit\n", &second);
13890         DoSleep( appData.delayAfterQuit );
13891         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13892     }
13893     if (first.isr != NULL) {
13894         RemoveInputSource(first.isr);
13895     }
13896     if (second.isr != NULL) {
13897         RemoveInputSource(second.isr);
13898     }
13899
13900     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13901     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13902
13903     ShutDownFrontEnd();
13904     exit(status);
13905 }
13906
13907 void
13908 PauseEngine (ChessProgramState *cps)
13909 {
13910     SendToProgram("pause\n", cps);
13911     cps->pause = 2;
13912 }
13913
13914 void
13915 UnPauseEngine (ChessProgramState *cps)
13916 {
13917     SendToProgram("resume\n", cps);
13918     cps->pause = 1;
13919 }
13920
13921 void
13922 PauseEvent ()
13923 {
13924     if (appData.debugMode)
13925         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13926     if (pausing) {
13927         pausing = FALSE;
13928         ModeHighlight();
13929         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13930             StartClocks();
13931             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13932                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13933                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13934             }
13935             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13936             HandleMachineMove(stashedInputMove, stalledEngine);
13937             stalledEngine = NULL;
13938             return;
13939         }
13940         if (gameMode == MachinePlaysWhite ||
13941             gameMode == TwoMachinesPlay   ||
13942             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13943             if(first.pause)  UnPauseEngine(&first);
13944             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13945             if(second.pause) UnPauseEngine(&second);
13946             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13947             StartClocks();
13948         } else {
13949             DisplayBothClocks();
13950         }
13951         if (gameMode == PlayFromGameFile) {
13952             if (appData.timeDelay >= 0)
13953                 AutoPlayGameLoop();
13954         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13955             Reset(FALSE, TRUE);
13956             SendToICS(ics_prefix);
13957             SendToICS("refresh\n");
13958         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13959             ForwardInner(forwardMostMove);
13960         }
13961         pauseExamInvalid = FALSE;
13962     } else {
13963         switch (gameMode) {
13964           default:
13965             return;
13966           case IcsExamining:
13967             pauseExamForwardMostMove = forwardMostMove;
13968             pauseExamInvalid = FALSE;
13969             /* fall through */
13970           case IcsObserving:
13971           case IcsPlayingWhite:
13972           case IcsPlayingBlack:
13973             pausing = TRUE;
13974             ModeHighlight();
13975             return;
13976           case PlayFromGameFile:
13977             (void) StopLoadGameTimer();
13978             pausing = TRUE;
13979             ModeHighlight();
13980             break;
13981           case BeginningOfGame:
13982             if (appData.icsActive) return;
13983             /* else fall through */
13984           case MachinePlaysWhite:
13985           case MachinePlaysBlack:
13986           case TwoMachinesPlay:
13987             if (forwardMostMove == 0)
13988               return;           /* don't pause if no one has moved */
13989             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13990                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13991                 if(onMove->pause) {           // thinking engine can be paused
13992                     PauseEngine(onMove);      // do it
13993                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13994                         PauseEngine(onMove->other);
13995                     else
13996                         SendToProgram("easy\n", onMove->other);
13997                     StopClocks();
13998                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13999             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14000                 if(first.pause) {
14001                     PauseEngine(&first);
14002                     StopClocks();
14003                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14004             } else { // human on move, pause pondering by either method
14005                 if(first.pause)
14006                     PauseEngine(&first);
14007                 else if(appData.ponderNextMove)
14008                     SendToProgram("easy\n", &first);
14009                 StopClocks();
14010             }
14011             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14012           case AnalyzeMode:
14013             pausing = TRUE;
14014             ModeHighlight();
14015             break;
14016         }
14017     }
14018 }
14019
14020 void
14021 EditCommentEvent ()
14022 {
14023     char title[MSG_SIZ];
14024
14025     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14026       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14027     } else {
14028       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14029                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14030                parseList[currentMove - 1]);
14031     }
14032
14033     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14034 }
14035
14036
14037 void
14038 EditTagsEvent ()
14039 {
14040     char *tags = PGNTags(&gameInfo);
14041     bookUp = FALSE;
14042     EditTagsPopUp(tags, NULL);
14043     free(tags);
14044 }
14045
14046 void
14047 ToggleSecond ()
14048 {
14049   if(second.analyzing) {
14050     SendToProgram("exit\n", &second);
14051     second.analyzing = FALSE;
14052   } else {
14053     if (second.pr == NoProc) StartChessProgram(&second);
14054     InitChessProgram(&second, FALSE);
14055     FeedMovesToProgram(&second, currentMove);
14056
14057     SendToProgram("analyze\n", &second);
14058     second.analyzing = TRUE;
14059   }
14060 }
14061
14062 /* Toggle ShowThinking */
14063 void
14064 ToggleShowThinking()
14065 {
14066   appData.showThinking = !appData.showThinking;
14067   ShowThinkingEvent();
14068 }
14069
14070 int
14071 AnalyzeModeEvent ()
14072 {
14073     char buf[MSG_SIZ];
14074
14075     if (!first.analysisSupport) {
14076       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14077       DisplayError(buf, 0);
14078       return 0;
14079     }
14080     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14081     if (appData.icsActive) {
14082         if (gameMode != IcsObserving) {
14083           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14084             DisplayError(buf, 0);
14085             /* secure check */
14086             if (appData.icsEngineAnalyze) {
14087                 if (appData.debugMode)
14088                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14089                 ExitAnalyzeMode();
14090                 ModeHighlight();
14091             }
14092             return 0;
14093         }
14094         /* if enable, user wants to disable icsEngineAnalyze */
14095         if (appData.icsEngineAnalyze) {
14096                 ExitAnalyzeMode();
14097                 ModeHighlight();
14098                 return 0;
14099         }
14100         appData.icsEngineAnalyze = TRUE;
14101         if (appData.debugMode)
14102             fprintf(debugFP, "ICS engine analyze starting... \n");
14103     }
14104
14105     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14106     if (appData.noChessProgram || gameMode == AnalyzeMode)
14107       return 0;
14108
14109     if (gameMode != AnalyzeFile) {
14110         if (!appData.icsEngineAnalyze) {
14111                EditGameEvent();
14112                if (gameMode != EditGame) return 0;
14113         }
14114         if (!appData.showThinking) ToggleShowThinking();
14115         ResurrectChessProgram();
14116         SendToProgram("analyze\n", &first);
14117         first.analyzing = TRUE;
14118         /*first.maybeThinking = TRUE;*/
14119         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14120         EngineOutputPopUp();
14121     }
14122     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14123     pausing = FALSE;
14124     ModeHighlight();
14125     SetGameInfo();
14126
14127     StartAnalysisClock();
14128     GetTimeMark(&lastNodeCountTime);
14129     lastNodeCount = 0;
14130     return 1;
14131 }
14132
14133 void
14134 AnalyzeFileEvent ()
14135 {
14136     if (appData.noChessProgram || gameMode == AnalyzeFile)
14137       return;
14138
14139     if (!first.analysisSupport) {
14140       char buf[MSG_SIZ];
14141       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14142       DisplayError(buf, 0);
14143       return;
14144     }
14145
14146     if (gameMode != AnalyzeMode) {
14147         keepInfo = 1; // mere annotating should not alter PGN tags
14148         EditGameEvent();
14149         keepInfo = 0;
14150         if (gameMode != EditGame) return;
14151         if (!appData.showThinking) ToggleShowThinking();
14152         ResurrectChessProgram();
14153         SendToProgram("analyze\n", &first);
14154         first.analyzing = TRUE;
14155         /*first.maybeThinking = TRUE;*/
14156         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14157         EngineOutputPopUp();
14158     }
14159     gameMode = AnalyzeFile;
14160     pausing = FALSE;
14161     ModeHighlight();
14162
14163     StartAnalysisClock();
14164     GetTimeMark(&lastNodeCountTime);
14165     lastNodeCount = 0;
14166     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14167     AnalysisPeriodicEvent(1);
14168 }
14169
14170 void
14171 MachineWhiteEvent ()
14172 {
14173     char buf[MSG_SIZ];
14174     char *bookHit = NULL;
14175
14176     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14177       return;
14178
14179
14180     if (gameMode == PlayFromGameFile ||
14181         gameMode == TwoMachinesPlay  ||
14182         gameMode == Training         ||
14183         gameMode == AnalyzeMode      ||
14184         gameMode == EndOfGame)
14185         EditGameEvent();
14186
14187     if (gameMode == EditPosition)
14188         EditPositionDone(TRUE);
14189
14190     if (!WhiteOnMove(currentMove)) {
14191         DisplayError(_("It is not White's turn"), 0);
14192         return;
14193     }
14194
14195     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14196       ExitAnalyzeMode();
14197
14198     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14199         gameMode == AnalyzeFile)
14200         TruncateGame();
14201
14202     ResurrectChessProgram();    /* in case it isn't running */
14203     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14204         gameMode = MachinePlaysWhite;
14205         ResetClocks();
14206     } else
14207     gameMode = MachinePlaysWhite;
14208     pausing = FALSE;
14209     ModeHighlight();
14210     SetGameInfo();
14211     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14212     DisplayTitle(buf);
14213     if (first.sendName) {
14214       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14215       SendToProgram(buf, &first);
14216     }
14217     if (first.sendTime) {
14218       if (first.useColors) {
14219         SendToProgram("black\n", &first); /*gnu kludge*/
14220       }
14221       SendTimeRemaining(&first, TRUE);
14222     }
14223     if (first.useColors) {
14224       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14225     }
14226     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14227     SetMachineThinkingEnables();
14228     first.maybeThinking = TRUE;
14229     StartClocks();
14230     firstMove = FALSE;
14231
14232     if (appData.autoFlipView && !flipView) {
14233       flipView = !flipView;
14234       DrawPosition(FALSE, NULL);
14235       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14236     }
14237
14238     if(bookHit) { // [HGM] book: simulate book reply
14239         static char bookMove[MSG_SIZ]; // a bit generous?
14240
14241         programStats.nodes = programStats.depth = programStats.time =
14242         programStats.score = programStats.got_only_move = 0;
14243         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14244
14245         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14246         strcat(bookMove, bookHit);
14247         HandleMachineMove(bookMove, &first);
14248     }
14249 }
14250
14251 void
14252 MachineBlackEvent ()
14253 {
14254   char buf[MSG_SIZ];
14255   char *bookHit = NULL;
14256
14257     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14258         return;
14259
14260
14261     if (gameMode == PlayFromGameFile ||
14262         gameMode == TwoMachinesPlay  ||
14263         gameMode == Training         ||
14264         gameMode == AnalyzeMode      ||
14265         gameMode == EndOfGame)
14266         EditGameEvent();
14267
14268     if (gameMode == EditPosition)
14269         EditPositionDone(TRUE);
14270
14271     if (WhiteOnMove(currentMove)) {
14272         DisplayError(_("It is not Black's turn"), 0);
14273         return;
14274     }
14275
14276     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14277       ExitAnalyzeMode();
14278
14279     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14280         gameMode == AnalyzeFile)
14281         TruncateGame();
14282
14283     ResurrectChessProgram();    /* in case it isn't running */
14284     gameMode = MachinePlaysBlack;
14285     pausing = FALSE;
14286     ModeHighlight();
14287     SetGameInfo();
14288     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14289     DisplayTitle(buf);
14290     if (first.sendName) {
14291       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14292       SendToProgram(buf, &first);
14293     }
14294     if (first.sendTime) {
14295       if (first.useColors) {
14296         SendToProgram("white\n", &first); /*gnu kludge*/
14297       }
14298       SendTimeRemaining(&first, FALSE);
14299     }
14300     if (first.useColors) {
14301       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14302     }
14303     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14304     SetMachineThinkingEnables();
14305     first.maybeThinking = TRUE;
14306     StartClocks();
14307
14308     if (appData.autoFlipView && flipView) {
14309       flipView = !flipView;
14310       DrawPosition(FALSE, NULL);
14311       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14312     }
14313     if(bookHit) { // [HGM] book: simulate book reply
14314         static char bookMove[MSG_SIZ]; // a bit generous?
14315
14316         programStats.nodes = programStats.depth = programStats.time =
14317         programStats.score = programStats.got_only_move = 0;
14318         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14319
14320         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14321         strcat(bookMove, bookHit);
14322         HandleMachineMove(bookMove, &first);
14323     }
14324 }
14325
14326
14327 void
14328 DisplayTwoMachinesTitle ()
14329 {
14330     char buf[MSG_SIZ];
14331     if (appData.matchGames > 0) {
14332         if(appData.tourneyFile[0]) {
14333           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14334                    gameInfo.white, _("vs."), gameInfo.black,
14335                    nextGame+1, appData.matchGames+1,
14336                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14337         } else
14338         if (first.twoMachinesColor[0] == 'w') {
14339           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14340                    gameInfo.white, _("vs."),  gameInfo.black,
14341                    first.matchWins, second.matchWins,
14342                    matchGame - 1 - (first.matchWins + second.matchWins));
14343         } else {
14344           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14345                    gameInfo.white, _("vs."), gameInfo.black,
14346                    second.matchWins, first.matchWins,
14347                    matchGame - 1 - (first.matchWins + second.matchWins));
14348         }
14349     } else {
14350       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14351     }
14352     DisplayTitle(buf);
14353 }
14354
14355 void
14356 SettingsMenuIfReady ()
14357 {
14358   if (second.lastPing != second.lastPong) {
14359     DisplayMessage("", _("Waiting for second chess program"));
14360     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14361     return;
14362   }
14363   ThawUI();
14364   DisplayMessage("", "");
14365   SettingsPopUp(&second);
14366 }
14367
14368 int
14369 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14370 {
14371     char buf[MSG_SIZ];
14372     if (cps->pr == NoProc) {
14373         StartChessProgram(cps);
14374         if (cps->protocolVersion == 1) {
14375           retry();
14376           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14377         } else {
14378           /* kludge: allow timeout for initial "feature" command */
14379           if(retry != TwoMachinesEventIfReady) FreezeUI();
14380           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14381           DisplayMessage("", buf);
14382           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14383         }
14384         return 1;
14385     }
14386     return 0;
14387 }
14388
14389 void
14390 TwoMachinesEvent P((void))
14391 {
14392     int i;
14393     char buf[MSG_SIZ];
14394     ChessProgramState *onmove;
14395     char *bookHit = NULL;
14396     static int stalling = 0;
14397     TimeMark now;
14398     long wait;
14399
14400     if (appData.noChessProgram) return;
14401
14402     switch (gameMode) {
14403       case TwoMachinesPlay:
14404         return;
14405       case MachinePlaysWhite:
14406       case MachinePlaysBlack:
14407         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14408             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14409             return;
14410         }
14411         /* fall through */
14412       case BeginningOfGame:
14413       case PlayFromGameFile:
14414       case EndOfGame:
14415         EditGameEvent();
14416         if (gameMode != EditGame) return;
14417         break;
14418       case EditPosition:
14419         EditPositionDone(TRUE);
14420         break;
14421       case AnalyzeMode:
14422       case AnalyzeFile:
14423         ExitAnalyzeMode();
14424         break;
14425       case EditGame:
14426       default:
14427         break;
14428     }
14429
14430 //    forwardMostMove = currentMove;
14431     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14432     startingEngine = TRUE;
14433
14434     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14435
14436     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14437     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14438       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14439       return;
14440     }
14441     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14442
14443     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14444                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14445         startingEngine = FALSE;
14446         DisplayError("second engine does not play this", 0);
14447         return;
14448     }
14449
14450     if(!stalling) {
14451       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14452       SendToProgram("force\n", &second);
14453       stalling = 1;
14454       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14455       return;
14456     }
14457     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14458     if(appData.matchPause>10000 || appData.matchPause<10)
14459                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14460     wait = SubtractTimeMarks(&now, &pauseStart);
14461     if(wait < appData.matchPause) {
14462         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14463         return;
14464     }
14465     // we are now committed to starting the game
14466     stalling = 0;
14467     DisplayMessage("", "");
14468     if (startedFromSetupPosition) {
14469         SendBoard(&second, backwardMostMove);
14470     if (appData.debugMode) {
14471         fprintf(debugFP, "Two Machines\n");
14472     }
14473     }
14474     for (i = backwardMostMove; i < forwardMostMove; i++) {
14475         SendMoveToProgram(i, &second);
14476     }
14477
14478     gameMode = TwoMachinesPlay;
14479     pausing = startingEngine = FALSE;
14480     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14481     SetGameInfo();
14482     DisplayTwoMachinesTitle();
14483     firstMove = TRUE;
14484     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14485         onmove = &first;
14486     } else {
14487         onmove = &second;
14488     }
14489     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14490     SendToProgram(first.computerString, &first);
14491     if (first.sendName) {
14492       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14493       SendToProgram(buf, &first);
14494     }
14495     SendToProgram(second.computerString, &second);
14496     if (second.sendName) {
14497       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14498       SendToProgram(buf, &second);
14499     }
14500
14501     ResetClocks();
14502     if (!first.sendTime || !second.sendTime) {
14503         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14504         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14505     }
14506     if (onmove->sendTime) {
14507       if (onmove->useColors) {
14508         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14509       }
14510       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14511     }
14512     if (onmove->useColors) {
14513       SendToProgram(onmove->twoMachinesColor, onmove);
14514     }
14515     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14516 //    SendToProgram("go\n", onmove);
14517     onmove->maybeThinking = TRUE;
14518     SetMachineThinkingEnables();
14519
14520     StartClocks();
14521
14522     if(bookHit) { // [HGM] book: simulate book reply
14523         static char bookMove[MSG_SIZ]; // a bit generous?
14524
14525         programStats.nodes = programStats.depth = programStats.time =
14526         programStats.score = programStats.got_only_move = 0;
14527         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14528
14529         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14530         strcat(bookMove, bookHit);
14531         savedMessage = bookMove; // args for deferred call
14532         savedState = onmove;
14533         ScheduleDelayedEvent(DeferredBookMove, 1);
14534     }
14535 }
14536
14537 void
14538 TrainingEvent ()
14539 {
14540     if (gameMode == Training) {
14541       SetTrainingModeOff();
14542       gameMode = PlayFromGameFile;
14543       DisplayMessage("", _("Training mode off"));
14544     } else {
14545       gameMode = Training;
14546       animateTraining = appData.animate;
14547
14548       /* make sure we are not already at the end of the game */
14549       if (currentMove < forwardMostMove) {
14550         SetTrainingModeOn();
14551         DisplayMessage("", _("Training mode on"));
14552       } else {
14553         gameMode = PlayFromGameFile;
14554         DisplayError(_("Already at end of game"), 0);
14555       }
14556     }
14557     ModeHighlight();
14558 }
14559
14560 void
14561 IcsClientEvent ()
14562 {
14563     if (!appData.icsActive) return;
14564     switch (gameMode) {
14565       case IcsPlayingWhite:
14566       case IcsPlayingBlack:
14567       case IcsObserving:
14568       case IcsIdle:
14569       case BeginningOfGame:
14570       case IcsExamining:
14571         return;
14572
14573       case EditGame:
14574         break;
14575
14576       case EditPosition:
14577         EditPositionDone(TRUE);
14578         break;
14579
14580       case AnalyzeMode:
14581       case AnalyzeFile:
14582         ExitAnalyzeMode();
14583         break;
14584
14585       default:
14586         EditGameEvent();
14587         break;
14588     }
14589
14590     gameMode = IcsIdle;
14591     ModeHighlight();
14592     return;
14593 }
14594
14595 void
14596 EditGameEvent ()
14597 {
14598     int i;
14599
14600     switch (gameMode) {
14601       case Training:
14602         SetTrainingModeOff();
14603         break;
14604       case MachinePlaysWhite:
14605       case MachinePlaysBlack:
14606       case BeginningOfGame:
14607         SendToProgram("force\n", &first);
14608         SetUserThinkingEnables();
14609         break;
14610       case PlayFromGameFile:
14611         (void) StopLoadGameTimer();
14612         if (gameFileFP != NULL) {
14613             gameFileFP = NULL;
14614         }
14615         break;
14616       case EditPosition:
14617         EditPositionDone(TRUE);
14618         break;
14619       case AnalyzeMode:
14620       case AnalyzeFile:
14621         ExitAnalyzeMode();
14622         SendToProgram("force\n", &first);
14623         break;
14624       case TwoMachinesPlay:
14625         GameEnds(EndOfFile, NULL, GE_PLAYER);
14626         ResurrectChessProgram();
14627         SetUserThinkingEnables();
14628         break;
14629       case EndOfGame:
14630         ResurrectChessProgram();
14631         break;
14632       case IcsPlayingBlack:
14633       case IcsPlayingWhite:
14634         DisplayError(_("Warning: You are still playing a game"), 0);
14635         break;
14636       case IcsObserving:
14637         DisplayError(_("Warning: You are still observing a game"), 0);
14638         break;
14639       case IcsExamining:
14640         DisplayError(_("Warning: You are still examining a game"), 0);
14641         break;
14642       case IcsIdle:
14643         break;
14644       case EditGame:
14645       default:
14646         return;
14647     }
14648
14649     pausing = FALSE;
14650     StopClocks();
14651     first.offeredDraw = second.offeredDraw = 0;
14652
14653     if (gameMode == PlayFromGameFile) {
14654         whiteTimeRemaining = timeRemaining[0][currentMove];
14655         blackTimeRemaining = timeRemaining[1][currentMove];
14656         DisplayTitle("");
14657     }
14658
14659     if (gameMode == MachinePlaysWhite ||
14660         gameMode == MachinePlaysBlack ||
14661         gameMode == TwoMachinesPlay ||
14662         gameMode == EndOfGame) {
14663         i = forwardMostMove;
14664         while (i > currentMove) {
14665             SendToProgram("undo\n", &first);
14666             i--;
14667         }
14668         if(!adjustedClock) {
14669         whiteTimeRemaining = timeRemaining[0][currentMove];
14670         blackTimeRemaining = timeRemaining[1][currentMove];
14671         DisplayBothClocks();
14672         }
14673         if (whiteFlag || blackFlag) {
14674             whiteFlag = blackFlag = 0;
14675         }
14676         DisplayTitle("");
14677     }
14678
14679     gameMode = EditGame;
14680     ModeHighlight();
14681     SetGameInfo();
14682 }
14683
14684
14685 void
14686 EditPositionEvent ()
14687 {
14688     if (gameMode == EditPosition) {
14689         EditGameEvent();
14690         return;
14691     }
14692
14693     EditGameEvent();
14694     if (gameMode != EditGame) return;
14695
14696     gameMode = EditPosition;
14697     ModeHighlight();
14698     SetGameInfo();
14699     if (currentMove > 0)
14700       CopyBoard(boards[0], boards[currentMove]);
14701
14702     blackPlaysFirst = !WhiteOnMove(currentMove);
14703     ResetClocks();
14704     currentMove = forwardMostMove = backwardMostMove = 0;
14705     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14706     DisplayMove(-1);
14707     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14708 }
14709
14710 void
14711 ExitAnalyzeMode ()
14712 {
14713     /* [DM] icsEngineAnalyze - possible call from other functions */
14714     if (appData.icsEngineAnalyze) {
14715         appData.icsEngineAnalyze = FALSE;
14716
14717         DisplayMessage("",_("Close ICS engine analyze..."));
14718     }
14719     if (first.analysisSupport && first.analyzing) {
14720       SendToBoth("exit\n");
14721       first.analyzing = second.analyzing = FALSE;
14722     }
14723     thinkOutput[0] = NULLCHAR;
14724 }
14725
14726 void
14727 EditPositionDone (Boolean fakeRights)
14728 {
14729     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14730
14731     startedFromSetupPosition = TRUE;
14732     InitChessProgram(&first, FALSE);
14733     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14734       boards[0][EP_STATUS] = EP_NONE;
14735       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14736       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14737         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14738         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14739       } else boards[0][CASTLING][2] = NoRights;
14740       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14741         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14742         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14743       } else boards[0][CASTLING][5] = NoRights;
14744       if(gameInfo.variant == VariantSChess) {
14745         int i;
14746         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14747           boards[0][VIRGIN][i] = 0;
14748           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14749           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14750         }
14751       }
14752     }
14753     SendToProgram("force\n", &first);
14754     if (blackPlaysFirst) {
14755         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14756         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14757         currentMove = forwardMostMove = backwardMostMove = 1;
14758         CopyBoard(boards[1], boards[0]);
14759     } else {
14760         currentMove = forwardMostMove = backwardMostMove = 0;
14761     }
14762     SendBoard(&first, forwardMostMove);
14763     if (appData.debugMode) {
14764         fprintf(debugFP, "EditPosDone\n");
14765     }
14766     DisplayTitle("");
14767     DisplayMessage("", "");
14768     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14769     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14770     gameMode = EditGame;
14771     ModeHighlight();
14772     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14773     ClearHighlights(); /* [AS] */
14774 }
14775
14776 /* Pause for `ms' milliseconds */
14777 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14778 void
14779 TimeDelay (long ms)
14780 {
14781     TimeMark m1, m2;
14782
14783     GetTimeMark(&m1);
14784     do {
14785         GetTimeMark(&m2);
14786     } while (SubtractTimeMarks(&m2, &m1) < ms);
14787 }
14788
14789 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14790 void
14791 SendMultiLineToICS (char *buf)
14792 {
14793     char temp[MSG_SIZ+1], *p;
14794     int len;
14795
14796     len = strlen(buf);
14797     if (len > MSG_SIZ)
14798       len = MSG_SIZ;
14799
14800     strncpy(temp, buf, len);
14801     temp[len] = 0;
14802
14803     p = temp;
14804     while (*p) {
14805         if (*p == '\n' || *p == '\r')
14806           *p = ' ';
14807         ++p;
14808     }
14809
14810     strcat(temp, "\n");
14811     SendToICS(temp);
14812     SendToPlayer(temp, strlen(temp));
14813 }
14814
14815 void
14816 SetWhiteToPlayEvent ()
14817 {
14818     if (gameMode == EditPosition) {
14819         blackPlaysFirst = FALSE;
14820         DisplayBothClocks();    /* works because currentMove is 0 */
14821     } else if (gameMode == IcsExamining) {
14822         SendToICS(ics_prefix);
14823         SendToICS("tomove white\n");
14824     }
14825 }
14826
14827 void
14828 SetBlackToPlayEvent ()
14829 {
14830     if (gameMode == EditPosition) {
14831         blackPlaysFirst = TRUE;
14832         currentMove = 1;        /* kludge */
14833         DisplayBothClocks();
14834         currentMove = 0;
14835     } else if (gameMode == IcsExamining) {
14836         SendToICS(ics_prefix);
14837         SendToICS("tomove black\n");
14838     }
14839 }
14840
14841 void
14842 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14843 {
14844     char buf[MSG_SIZ];
14845     ChessSquare piece = boards[0][y][x];
14846     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14847     static int lastVariant;
14848
14849     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14850
14851     switch (selection) {
14852       case ClearBoard:
14853         CopyBoard(currentBoard, boards[0]);
14854         CopyBoard(menuBoard, initialPosition);
14855         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14856             SendToICS(ics_prefix);
14857             SendToICS("bsetup clear\n");
14858         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14859             SendToICS(ics_prefix);
14860             SendToICS("clearboard\n");
14861         } else {
14862             int nonEmpty = 0;
14863             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14864                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14865                 for (y = 0; y < BOARD_HEIGHT; y++) {
14866                     if (gameMode == IcsExamining) {
14867                         if (boards[currentMove][y][x] != EmptySquare) {
14868                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14869                                     AAA + x, ONE + y);
14870                             SendToICS(buf);
14871                         }
14872                     } else {
14873                         if(boards[0][y][x] != p) nonEmpty++;
14874                         boards[0][y][x] = p;
14875                     }
14876                 }
14877                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14878             }
14879             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14880                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14881                     ChessSquare p = menuBoard[0][x];
14882                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14883                     p = menuBoard[BOARD_HEIGHT-1][x];
14884                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14885                 }
14886                 DisplayMessage("Clicking clock again restores position", "");
14887                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14888                 if(!nonEmpty) { // asked to clear an empty board
14889                     CopyBoard(boards[0], menuBoard);
14890                 } else
14891                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14892                     CopyBoard(boards[0], initialPosition);
14893                 } else
14894                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14895                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14896                     CopyBoard(boards[0], erasedBoard);
14897                 } else
14898                     CopyBoard(erasedBoard, currentBoard);
14899
14900             }
14901         }
14902         if (gameMode == EditPosition) {
14903             DrawPosition(FALSE, boards[0]);
14904         }
14905         break;
14906
14907       case WhitePlay:
14908         SetWhiteToPlayEvent();
14909         break;
14910
14911       case BlackPlay:
14912         SetBlackToPlayEvent();
14913         break;
14914
14915       case EmptySquare:
14916         if (gameMode == IcsExamining) {
14917             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14918             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14919             SendToICS(buf);
14920         } else {
14921             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14922                 if(x == BOARD_LEFT-2) {
14923                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14924                     boards[0][y][1] = 0;
14925                 } else
14926                 if(x == BOARD_RGHT+1) {
14927                     if(y >= gameInfo.holdingsSize) break;
14928                     boards[0][y][BOARD_WIDTH-2] = 0;
14929                 } else break;
14930             }
14931             boards[0][y][x] = EmptySquare;
14932             DrawPosition(FALSE, boards[0]);
14933         }
14934         break;
14935
14936       case PromotePiece:
14937         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14938            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14939             selection = (ChessSquare) (PROMOTED piece);
14940         } else if(piece == EmptySquare) selection = WhiteSilver;
14941         else selection = (ChessSquare)((int)piece - 1);
14942         goto defaultlabel;
14943
14944       case DemotePiece:
14945         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14946            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14947             selection = (ChessSquare) (DEMOTED piece);
14948         } else if(piece == EmptySquare) selection = BlackSilver;
14949         else selection = (ChessSquare)((int)piece + 1);
14950         goto defaultlabel;
14951
14952       case WhiteQueen:
14953       case BlackQueen:
14954         if(gameInfo.variant == VariantShatranj ||
14955            gameInfo.variant == VariantXiangqi  ||
14956            gameInfo.variant == VariantCourier  ||
14957            gameInfo.variant == VariantASEAN    ||
14958            gameInfo.variant == VariantMakruk     )
14959             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14960         goto defaultlabel;
14961
14962       case WhiteKing:
14963       case BlackKing:
14964         if(gameInfo.variant == VariantXiangqi)
14965             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14966         if(gameInfo.variant == VariantKnightmate)
14967             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14968       default:
14969         defaultlabel:
14970         if (gameMode == IcsExamining) {
14971             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14972             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14973                      PieceToChar(selection), AAA + x, ONE + y);
14974             SendToICS(buf);
14975         } else {
14976             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14977                 int n;
14978                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14979                     n = PieceToNumber(selection - BlackPawn);
14980                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14981                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14982                     boards[0][BOARD_HEIGHT-1-n][1]++;
14983                 } else
14984                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14985                     n = PieceToNumber(selection);
14986                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14987                     boards[0][n][BOARD_WIDTH-1] = selection;
14988                     boards[0][n][BOARD_WIDTH-2]++;
14989                 }
14990             } else
14991             boards[0][y][x] = selection;
14992             DrawPosition(TRUE, boards[0]);
14993             ClearHighlights();
14994             fromX = fromY = -1;
14995         }
14996         break;
14997     }
14998 }
14999
15000
15001 void
15002 DropMenuEvent (ChessSquare selection, int x, int y)
15003 {
15004     ChessMove moveType;
15005
15006     switch (gameMode) {
15007       case IcsPlayingWhite:
15008       case MachinePlaysBlack:
15009         if (!WhiteOnMove(currentMove)) {
15010             DisplayMoveError(_("It is Black's turn"));
15011             return;
15012         }
15013         moveType = WhiteDrop;
15014         break;
15015       case IcsPlayingBlack:
15016       case MachinePlaysWhite:
15017         if (WhiteOnMove(currentMove)) {
15018             DisplayMoveError(_("It is White's turn"));
15019             return;
15020         }
15021         moveType = BlackDrop;
15022         break;
15023       case EditGame:
15024         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15025         break;
15026       default:
15027         return;
15028     }
15029
15030     if (moveType == BlackDrop && selection < BlackPawn) {
15031       selection = (ChessSquare) ((int) selection
15032                                  + (int) BlackPawn - (int) WhitePawn);
15033     }
15034     if (boards[currentMove][y][x] != EmptySquare) {
15035         DisplayMoveError(_("That square is occupied"));
15036         return;
15037     }
15038
15039     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15040 }
15041
15042 void
15043 AcceptEvent ()
15044 {
15045     /* Accept a pending offer of any kind from opponent */
15046
15047     if (appData.icsActive) {
15048         SendToICS(ics_prefix);
15049         SendToICS("accept\n");
15050     } else if (cmailMsgLoaded) {
15051         if (currentMove == cmailOldMove &&
15052             commentList[cmailOldMove] != NULL &&
15053             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15054                    "Black offers a draw" : "White offers a draw")) {
15055             TruncateGame();
15056             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15057             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15058         } else {
15059             DisplayError(_("There is no pending offer on this move"), 0);
15060             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15061         }
15062     } else {
15063         /* Not used for offers from chess program */
15064     }
15065 }
15066
15067 void
15068 DeclineEvent ()
15069 {
15070     /* Decline a pending offer of any kind from opponent */
15071
15072     if (appData.icsActive) {
15073         SendToICS(ics_prefix);
15074         SendToICS("decline\n");
15075     } else if (cmailMsgLoaded) {
15076         if (currentMove == cmailOldMove &&
15077             commentList[cmailOldMove] != NULL &&
15078             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15079                    "Black offers a draw" : "White offers a draw")) {
15080 #ifdef NOTDEF
15081             AppendComment(cmailOldMove, "Draw declined", TRUE);
15082             DisplayComment(cmailOldMove - 1, "Draw declined");
15083 #endif /*NOTDEF*/
15084         } else {
15085             DisplayError(_("There is no pending offer on this move"), 0);
15086         }
15087     } else {
15088         /* Not used for offers from chess program */
15089     }
15090 }
15091
15092 void
15093 RematchEvent ()
15094 {
15095     /* Issue ICS rematch command */
15096     if (appData.icsActive) {
15097         SendToICS(ics_prefix);
15098         SendToICS("rematch\n");
15099     }
15100 }
15101
15102 void
15103 CallFlagEvent ()
15104 {
15105     /* Call your opponent's flag (claim a win on time) */
15106     if (appData.icsActive) {
15107         SendToICS(ics_prefix);
15108         SendToICS("flag\n");
15109     } else {
15110         switch (gameMode) {
15111           default:
15112             return;
15113           case MachinePlaysWhite:
15114             if (whiteFlag) {
15115                 if (blackFlag)
15116                   GameEnds(GameIsDrawn, "Both players ran out of time",
15117                            GE_PLAYER);
15118                 else
15119                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15120             } else {
15121                 DisplayError(_("Your opponent is not out of time"), 0);
15122             }
15123             break;
15124           case MachinePlaysBlack:
15125             if (blackFlag) {
15126                 if (whiteFlag)
15127                   GameEnds(GameIsDrawn, "Both players ran out of time",
15128                            GE_PLAYER);
15129                 else
15130                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15131             } else {
15132                 DisplayError(_("Your opponent is not out of time"), 0);
15133             }
15134             break;
15135         }
15136     }
15137 }
15138
15139 void
15140 ClockClick (int which)
15141 {       // [HGM] code moved to back-end from winboard.c
15142         if(which) { // black clock
15143           if (gameMode == EditPosition || gameMode == IcsExamining) {
15144             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15145             SetBlackToPlayEvent();
15146           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15147           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15148           } else if (shiftKey) {
15149             AdjustClock(which, -1);
15150           } else if (gameMode == IcsPlayingWhite ||
15151                      gameMode == MachinePlaysBlack) {
15152             CallFlagEvent();
15153           }
15154         } else { // white clock
15155           if (gameMode == EditPosition || gameMode == IcsExamining) {
15156             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15157             SetWhiteToPlayEvent();
15158           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15159           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15160           } else if (shiftKey) {
15161             AdjustClock(which, -1);
15162           } else if (gameMode == IcsPlayingBlack ||
15163                    gameMode == MachinePlaysWhite) {
15164             CallFlagEvent();
15165           }
15166         }
15167 }
15168
15169 void
15170 DrawEvent ()
15171 {
15172     /* Offer draw or accept pending draw offer from opponent */
15173
15174     if (appData.icsActive) {
15175         /* Note: tournament rules require draw offers to be
15176            made after you make your move but before you punch
15177            your clock.  Currently ICS doesn't let you do that;
15178            instead, you immediately punch your clock after making
15179            a move, but you can offer a draw at any time. */
15180
15181         SendToICS(ics_prefix);
15182         SendToICS("draw\n");
15183         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15184     } else if (cmailMsgLoaded) {
15185         if (currentMove == cmailOldMove &&
15186             commentList[cmailOldMove] != NULL &&
15187             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15188                    "Black offers a draw" : "White offers a draw")) {
15189             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15190             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15191         } else if (currentMove == cmailOldMove + 1) {
15192             char *offer = WhiteOnMove(cmailOldMove) ?
15193               "White offers a draw" : "Black offers a draw";
15194             AppendComment(currentMove, offer, TRUE);
15195             DisplayComment(currentMove - 1, offer);
15196             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15197         } else {
15198             DisplayError(_("You must make your move before offering a draw"), 0);
15199             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15200         }
15201     } else if (first.offeredDraw) {
15202         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15203     } else {
15204         if (first.sendDrawOffers) {
15205             SendToProgram("draw\n", &first);
15206             userOfferedDraw = TRUE;
15207         }
15208     }
15209 }
15210
15211 void
15212 AdjournEvent ()
15213 {
15214     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15215
15216     if (appData.icsActive) {
15217         SendToICS(ics_prefix);
15218         SendToICS("adjourn\n");
15219     } else {
15220         /* Currently GNU Chess doesn't offer or accept Adjourns */
15221     }
15222 }
15223
15224
15225 void
15226 AbortEvent ()
15227 {
15228     /* Offer Abort or accept pending Abort offer from opponent */
15229
15230     if (appData.icsActive) {
15231         SendToICS(ics_prefix);
15232         SendToICS("abort\n");
15233     } else {
15234         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15235     }
15236 }
15237
15238 void
15239 ResignEvent ()
15240 {
15241     /* Resign.  You can do this even if it's not your turn. */
15242
15243     if (appData.icsActive) {
15244         SendToICS(ics_prefix);
15245         SendToICS("resign\n");
15246     } else {
15247         switch (gameMode) {
15248           case MachinePlaysWhite:
15249             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15250             break;
15251           case MachinePlaysBlack:
15252             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15253             break;
15254           case EditGame:
15255             if (cmailMsgLoaded) {
15256                 TruncateGame();
15257                 if (WhiteOnMove(cmailOldMove)) {
15258                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15259                 } else {
15260                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15261                 }
15262                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15263             }
15264             break;
15265           default:
15266             break;
15267         }
15268     }
15269 }
15270
15271
15272 void
15273 StopObservingEvent ()
15274 {
15275     /* Stop observing current games */
15276     SendToICS(ics_prefix);
15277     SendToICS("unobserve\n");
15278 }
15279
15280 void
15281 StopExaminingEvent ()
15282 {
15283     /* Stop observing current game */
15284     SendToICS(ics_prefix);
15285     SendToICS("unexamine\n");
15286 }
15287
15288 void
15289 ForwardInner (int target)
15290 {
15291     int limit; int oldSeekGraphUp = seekGraphUp;
15292
15293     if (appData.debugMode)
15294         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15295                 target, currentMove, forwardMostMove);
15296
15297     if (gameMode == EditPosition)
15298       return;
15299
15300     seekGraphUp = FALSE;
15301     MarkTargetSquares(1);
15302
15303     if (gameMode == PlayFromGameFile && !pausing)
15304       PauseEvent();
15305
15306     if (gameMode == IcsExamining && pausing)
15307       limit = pauseExamForwardMostMove;
15308     else
15309       limit = forwardMostMove;
15310
15311     if (target > limit) target = limit;
15312
15313     if (target > 0 && moveList[target - 1][0]) {
15314         int fromX, fromY, toX, toY;
15315         toX = moveList[target - 1][2] - AAA;
15316         toY = moveList[target - 1][3] - ONE;
15317         if (moveList[target - 1][1] == '@') {
15318             if (appData.highlightLastMove) {
15319                 SetHighlights(-1, -1, toX, toY);
15320             }
15321         } else {
15322             fromX = moveList[target - 1][0] - AAA;
15323             fromY = moveList[target - 1][1] - ONE;
15324             if (target == currentMove + 1) {
15325                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15326             }
15327             if (appData.highlightLastMove) {
15328                 SetHighlights(fromX, fromY, toX, toY);
15329             }
15330         }
15331     }
15332     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15333         gameMode == Training || gameMode == PlayFromGameFile ||
15334         gameMode == AnalyzeFile) {
15335         while (currentMove < target) {
15336             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15337             SendMoveToProgram(currentMove++, &first);
15338         }
15339     } else {
15340         currentMove = target;
15341     }
15342
15343     if (gameMode == EditGame || gameMode == EndOfGame) {
15344         whiteTimeRemaining = timeRemaining[0][currentMove];
15345         blackTimeRemaining = timeRemaining[1][currentMove];
15346     }
15347     DisplayBothClocks();
15348     DisplayMove(currentMove - 1);
15349     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15350     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15351     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15352         DisplayComment(currentMove - 1, commentList[currentMove]);
15353     }
15354     ClearMap(); // [HGM] exclude: invalidate map
15355 }
15356
15357
15358 void
15359 ForwardEvent ()
15360 {
15361     if (gameMode == IcsExamining && !pausing) {
15362         SendToICS(ics_prefix);
15363         SendToICS("forward\n");
15364     } else {
15365         ForwardInner(currentMove + 1);
15366     }
15367 }
15368
15369 void
15370 ToEndEvent ()
15371 {
15372     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15373         /* to optimze, we temporarily turn off analysis mode while we feed
15374          * the remaining moves to the engine. Otherwise we get analysis output
15375          * after each move.
15376          */
15377         if (first.analysisSupport) {
15378           SendToProgram("exit\nforce\n", &first);
15379           first.analyzing = FALSE;
15380         }
15381     }
15382
15383     if (gameMode == IcsExamining && !pausing) {
15384         SendToICS(ics_prefix);
15385         SendToICS("forward 999999\n");
15386     } else {
15387         ForwardInner(forwardMostMove);
15388     }
15389
15390     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15391         /* we have fed all the moves, so reactivate analysis mode */
15392         SendToProgram("analyze\n", &first);
15393         first.analyzing = TRUE;
15394         /*first.maybeThinking = TRUE;*/
15395         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15396     }
15397 }
15398
15399 void
15400 BackwardInner (int target)
15401 {
15402     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15403
15404     if (appData.debugMode)
15405         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15406                 target, currentMove, forwardMostMove);
15407
15408     if (gameMode == EditPosition) return;
15409     seekGraphUp = FALSE;
15410     MarkTargetSquares(1);
15411     if (currentMove <= backwardMostMove) {
15412         ClearHighlights();
15413         DrawPosition(full_redraw, boards[currentMove]);
15414         return;
15415     }
15416     if (gameMode == PlayFromGameFile && !pausing)
15417       PauseEvent();
15418
15419     if (moveList[target][0]) {
15420         int fromX, fromY, toX, toY;
15421         toX = moveList[target][2] - AAA;
15422         toY = moveList[target][3] - ONE;
15423         if (moveList[target][1] == '@') {
15424             if (appData.highlightLastMove) {
15425                 SetHighlights(-1, -1, toX, toY);
15426             }
15427         } else {
15428             fromX = moveList[target][0] - AAA;
15429             fromY = moveList[target][1] - ONE;
15430             if (target == currentMove - 1) {
15431                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15432             }
15433             if (appData.highlightLastMove) {
15434                 SetHighlights(fromX, fromY, toX, toY);
15435             }
15436         }
15437     }
15438     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15439         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15440         while (currentMove > target) {
15441             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15442                 // null move cannot be undone. Reload program with move history before it.
15443                 int i;
15444                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15445                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15446                 }
15447                 SendBoard(&first, i);
15448               if(second.analyzing) SendBoard(&second, i);
15449                 for(currentMove=i; currentMove<target; currentMove++) {
15450                     SendMoveToProgram(currentMove, &first);
15451                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15452                 }
15453                 break;
15454             }
15455             SendToBoth("undo\n");
15456             currentMove--;
15457         }
15458     } else {
15459         currentMove = target;
15460     }
15461
15462     if (gameMode == EditGame || gameMode == EndOfGame) {
15463         whiteTimeRemaining = timeRemaining[0][currentMove];
15464         blackTimeRemaining = timeRemaining[1][currentMove];
15465     }
15466     DisplayBothClocks();
15467     DisplayMove(currentMove - 1);
15468     DrawPosition(full_redraw, boards[currentMove]);
15469     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15470     // [HGM] PV info: routine tests if comment empty
15471     DisplayComment(currentMove - 1, commentList[currentMove]);
15472     ClearMap(); // [HGM] exclude: invalidate map
15473 }
15474
15475 void
15476 BackwardEvent ()
15477 {
15478     if (gameMode == IcsExamining && !pausing) {
15479         SendToICS(ics_prefix);
15480         SendToICS("backward\n");
15481     } else {
15482         BackwardInner(currentMove - 1);
15483     }
15484 }
15485
15486 void
15487 ToStartEvent ()
15488 {
15489     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15490         /* to optimize, we temporarily turn off analysis mode while we undo
15491          * all the moves. Otherwise we get analysis output after each undo.
15492          */
15493         if (first.analysisSupport) {
15494           SendToProgram("exit\nforce\n", &first);
15495           first.analyzing = FALSE;
15496         }
15497     }
15498
15499     if (gameMode == IcsExamining && !pausing) {
15500         SendToICS(ics_prefix);
15501         SendToICS("backward 999999\n");
15502     } else {
15503         BackwardInner(backwardMostMove);
15504     }
15505
15506     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15507         /* we have fed all the moves, so reactivate analysis mode */
15508         SendToProgram("analyze\n", &first);
15509         first.analyzing = TRUE;
15510         /*first.maybeThinking = TRUE;*/
15511         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15512     }
15513 }
15514
15515 void
15516 ToNrEvent (int to)
15517 {
15518   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15519   if (to >= forwardMostMove) to = forwardMostMove;
15520   if (to <= backwardMostMove) to = backwardMostMove;
15521   if (to < currentMove) {
15522     BackwardInner(to);
15523   } else {
15524     ForwardInner(to);
15525   }
15526 }
15527
15528 void
15529 RevertEvent (Boolean annotate)
15530 {
15531     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15532         return;
15533     }
15534     if (gameMode != IcsExamining) {
15535         DisplayError(_("You are not examining a game"), 0);
15536         return;
15537     }
15538     if (pausing) {
15539         DisplayError(_("You can't revert while pausing"), 0);
15540         return;
15541     }
15542     SendToICS(ics_prefix);
15543     SendToICS("revert\n");
15544 }
15545
15546 void
15547 RetractMoveEvent ()
15548 {
15549     switch (gameMode) {
15550       case MachinePlaysWhite:
15551       case MachinePlaysBlack:
15552         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15553             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15554             return;
15555         }
15556         if (forwardMostMove < 2) return;
15557         currentMove = forwardMostMove = forwardMostMove - 2;
15558         whiteTimeRemaining = timeRemaining[0][currentMove];
15559         blackTimeRemaining = timeRemaining[1][currentMove];
15560         DisplayBothClocks();
15561         DisplayMove(currentMove - 1);
15562         ClearHighlights();/*!! could figure this out*/
15563         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15564         SendToProgram("remove\n", &first);
15565         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15566         break;
15567
15568       case BeginningOfGame:
15569       default:
15570         break;
15571
15572       case IcsPlayingWhite:
15573       case IcsPlayingBlack:
15574         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15575             SendToICS(ics_prefix);
15576             SendToICS("takeback 2\n");
15577         } else {
15578             SendToICS(ics_prefix);
15579             SendToICS("takeback 1\n");
15580         }
15581         break;
15582     }
15583 }
15584
15585 void
15586 MoveNowEvent ()
15587 {
15588     ChessProgramState *cps;
15589
15590     switch (gameMode) {
15591       case MachinePlaysWhite:
15592         if (!WhiteOnMove(forwardMostMove)) {
15593             DisplayError(_("It is your turn"), 0);
15594             return;
15595         }
15596         cps = &first;
15597         break;
15598       case MachinePlaysBlack:
15599         if (WhiteOnMove(forwardMostMove)) {
15600             DisplayError(_("It is your turn"), 0);
15601             return;
15602         }
15603         cps = &first;
15604         break;
15605       case TwoMachinesPlay:
15606         if (WhiteOnMove(forwardMostMove) ==
15607             (first.twoMachinesColor[0] == 'w')) {
15608             cps = &first;
15609         } else {
15610             cps = &second;
15611         }
15612         break;
15613       case BeginningOfGame:
15614       default:
15615         return;
15616     }
15617     SendToProgram("?\n", cps);
15618 }
15619
15620 void
15621 TruncateGameEvent ()
15622 {
15623     EditGameEvent();
15624     if (gameMode != EditGame) return;
15625     TruncateGame();
15626 }
15627
15628 void
15629 TruncateGame ()
15630 {
15631     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15632     if (forwardMostMove > currentMove) {
15633         if (gameInfo.resultDetails != NULL) {
15634             free(gameInfo.resultDetails);
15635             gameInfo.resultDetails = NULL;
15636             gameInfo.result = GameUnfinished;
15637         }
15638         forwardMostMove = currentMove;
15639         HistorySet(parseList, backwardMostMove, forwardMostMove,
15640                    currentMove-1);
15641     }
15642 }
15643
15644 void
15645 HintEvent ()
15646 {
15647     if (appData.noChessProgram) return;
15648     switch (gameMode) {
15649       case MachinePlaysWhite:
15650         if (WhiteOnMove(forwardMostMove)) {
15651             DisplayError(_("Wait until your turn."), 0);
15652             return;
15653         }
15654         break;
15655       case BeginningOfGame:
15656       case MachinePlaysBlack:
15657         if (!WhiteOnMove(forwardMostMove)) {
15658             DisplayError(_("Wait until your turn."), 0);
15659             return;
15660         }
15661         break;
15662       default:
15663         DisplayError(_("No hint available"), 0);
15664         return;
15665     }
15666     SendToProgram("hint\n", &first);
15667     hintRequested = TRUE;
15668 }
15669
15670 void
15671 CreateBookEvent ()
15672 {
15673     ListGame * lg = (ListGame *) gameList.head;
15674     FILE *f, *g;
15675     int nItem;
15676     static int secondTime = FALSE;
15677
15678     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15679         DisplayError(_("Game list not loaded or empty"), 0);
15680         return;
15681     }
15682
15683     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15684         fclose(g);
15685         secondTime++;
15686         DisplayNote(_("Book file exists! Try again for overwrite."));
15687         return;
15688     }
15689
15690     creatingBook = TRUE;
15691     secondTime = FALSE;
15692
15693     /* Get list size */
15694     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15695         LoadGame(f, nItem, "", TRUE);
15696         AddGameToBook(TRUE);
15697         lg = (ListGame *) lg->node.succ;
15698     }
15699
15700     creatingBook = FALSE;
15701     FlushBook();
15702 }
15703
15704 void
15705 BookEvent ()
15706 {
15707     if (appData.noChessProgram) return;
15708     switch (gameMode) {
15709       case MachinePlaysWhite:
15710         if (WhiteOnMove(forwardMostMove)) {
15711             DisplayError(_("Wait until your turn."), 0);
15712             return;
15713         }
15714         break;
15715       case BeginningOfGame:
15716       case MachinePlaysBlack:
15717         if (!WhiteOnMove(forwardMostMove)) {
15718             DisplayError(_("Wait until your turn."), 0);
15719             return;
15720         }
15721         break;
15722       case EditPosition:
15723         EditPositionDone(TRUE);
15724         break;
15725       case TwoMachinesPlay:
15726         return;
15727       default:
15728         break;
15729     }
15730     SendToProgram("bk\n", &first);
15731     bookOutput[0] = NULLCHAR;
15732     bookRequested = TRUE;
15733 }
15734
15735 void
15736 AboutGameEvent ()
15737 {
15738     char *tags = PGNTags(&gameInfo);
15739     TagsPopUp(tags, CmailMsg());
15740     free(tags);
15741 }
15742
15743 /* end button procedures */
15744
15745 void
15746 PrintPosition (FILE *fp, int move)
15747 {
15748     int i, j;
15749
15750     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15751         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15752             char c = PieceToChar(boards[move][i][j]);
15753             fputc(c == 'x' ? '.' : c, fp);
15754             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15755         }
15756     }
15757     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15758       fprintf(fp, "white to play\n");
15759     else
15760       fprintf(fp, "black to play\n");
15761 }
15762
15763 void
15764 PrintOpponents (FILE *fp)
15765 {
15766     if (gameInfo.white != NULL) {
15767         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15768     } else {
15769         fprintf(fp, "\n");
15770     }
15771 }
15772
15773 /* Find last component of program's own name, using some heuristics */
15774 void
15775 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15776 {
15777     char *p, *q, c;
15778     int local = (strcmp(host, "localhost") == 0);
15779     while (!local && (p = strchr(prog, ';')) != NULL) {
15780         p++;
15781         while (*p == ' ') p++;
15782         prog = p;
15783     }
15784     if (*prog == '"' || *prog == '\'') {
15785         q = strchr(prog + 1, *prog);
15786     } else {
15787         q = strchr(prog, ' ');
15788     }
15789     if (q == NULL) q = prog + strlen(prog);
15790     p = q;
15791     while (p >= prog && *p != '/' && *p != '\\') p--;
15792     p++;
15793     if(p == prog && *p == '"') p++;
15794     c = *q; *q = 0;
15795     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15796     memcpy(buf, p, q - p);
15797     buf[q - p] = NULLCHAR;
15798     if (!local) {
15799         strcat(buf, "@");
15800         strcat(buf, host);
15801     }
15802 }
15803
15804 char *
15805 TimeControlTagValue ()
15806 {
15807     char buf[MSG_SIZ];
15808     if (!appData.clockMode) {
15809       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15810     } else if (movesPerSession > 0) {
15811       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15812     } else if (timeIncrement == 0) {
15813       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15814     } else {
15815       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15816     }
15817     return StrSave(buf);
15818 }
15819
15820 void
15821 SetGameInfo ()
15822 {
15823     /* This routine is used only for certain modes */
15824     VariantClass v = gameInfo.variant;
15825     ChessMove r = GameUnfinished;
15826     char *p = NULL;
15827
15828     if(keepInfo) return;
15829
15830     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15831         r = gameInfo.result;
15832         p = gameInfo.resultDetails;
15833         gameInfo.resultDetails = NULL;
15834     }
15835     ClearGameInfo(&gameInfo);
15836     gameInfo.variant = v;
15837
15838     switch (gameMode) {
15839       case MachinePlaysWhite:
15840         gameInfo.event = StrSave( appData.pgnEventHeader );
15841         gameInfo.site = StrSave(HostName());
15842         gameInfo.date = PGNDate();
15843         gameInfo.round = StrSave("-");
15844         gameInfo.white = StrSave(first.tidy);
15845         gameInfo.black = StrSave(UserName());
15846         gameInfo.timeControl = TimeControlTagValue();
15847         break;
15848
15849       case MachinePlaysBlack:
15850         gameInfo.event = StrSave( appData.pgnEventHeader );
15851         gameInfo.site = StrSave(HostName());
15852         gameInfo.date = PGNDate();
15853         gameInfo.round = StrSave("-");
15854         gameInfo.white = StrSave(UserName());
15855         gameInfo.black = StrSave(first.tidy);
15856         gameInfo.timeControl = TimeControlTagValue();
15857         break;
15858
15859       case TwoMachinesPlay:
15860         gameInfo.event = StrSave( appData.pgnEventHeader );
15861         gameInfo.site = StrSave(HostName());
15862         gameInfo.date = PGNDate();
15863         if (roundNr > 0) {
15864             char buf[MSG_SIZ];
15865             snprintf(buf, MSG_SIZ, "%d", roundNr);
15866             gameInfo.round = StrSave(buf);
15867         } else {
15868             gameInfo.round = StrSave("-");
15869         }
15870         if (first.twoMachinesColor[0] == 'w') {
15871             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15872             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15873         } else {
15874             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15875             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15876         }
15877         gameInfo.timeControl = TimeControlTagValue();
15878         break;
15879
15880       case EditGame:
15881         gameInfo.event = StrSave("Edited game");
15882         gameInfo.site = StrSave(HostName());
15883         gameInfo.date = PGNDate();
15884         gameInfo.round = StrSave("-");
15885         gameInfo.white = StrSave("-");
15886         gameInfo.black = StrSave("-");
15887         gameInfo.result = r;
15888         gameInfo.resultDetails = p;
15889         break;
15890
15891       case EditPosition:
15892         gameInfo.event = StrSave("Edited position");
15893         gameInfo.site = StrSave(HostName());
15894         gameInfo.date = PGNDate();
15895         gameInfo.round = StrSave("-");
15896         gameInfo.white = StrSave("-");
15897         gameInfo.black = StrSave("-");
15898         break;
15899
15900       case IcsPlayingWhite:
15901       case IcsPlayingBlack:
15902       case IcsObserving:
15903       case IcsExamining:
15904         break;
15905
15906       case PlayFromGameFile:
15907         gameInfo.event = StrSave("Game from non-PGN file");
15908         gameInfo.site = StrSave(HostName());
15909         gameInfo.date = PGNDate();
15910         gameInfo.round = StrSave("-");
15911         gameInfo.white = StrSave("?");
15912         gameInfo.black = StrSave("?");
15913         break;
15914
15915       default:
15916         break;
15917     }
15918 }
15919
15920 void
15921 ReplaceComment (int index, char *text)
15922 {
15923     int len;
15924     char *p;
15925     float score;
15926
15927     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15928        pvInfoList[index-1].depth == len &&
15929        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15930        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15931     while (*text == '\n') text++;
15932     len = strlen(text);
15933     while (len > 0 && text[len - 1] == '\n') len--;
15934
15935     if (commentList[index] != NULL)
15936       free(commentList[index]);
15937
15938     if (len == 0) {
15939         commentList[index] = NULL;
15940         return;
15941     }
15942   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15943       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15944       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15945     commentList[index] = (char *) malloc(len + 2);
15946     strncpy(commentList[index], text, len);
15947     commentList[index][len] = '\n';
15948     commentList[index][len + 1] = NULLCHAR;
15949   } else {
15950     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15951     char *p;
15952     commentList[index] = (char *) malloc(len + 7);
15953     safeStrCpy(commentList[index], "{\n", 3);
15954     safeStrCpy(commentList[index]+2, text, len+1);
15955     commentList[index][len+2] = NULLCHAR;
15956     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15957     strcat(commentList[index], "\n}\n");
15958   }
15959 }
15960
15961 void
15962 CrushCRs (char *text)
15963 {
15964   char *p = text;
15965   char *q = text;
15966   char ch;
15967
15968   do {
15969     ch = *p++;
15970     if (ch == '\r') continue;
15971     *q++ = ch;
15972   } while (ch != '\0');
15973 }
15974
15975 void
15976 AppendComment (int index, char *text, Boolean addBraces)
15977 /* addBraces  tells if we should add {} */
15978 {
15979     int oldlen, len;
15980     char *old;
15981
15982 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15983     if(addBraces == 3) addBraces = 0; else // force appending literally
15984     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15985
15986     CrushCRs(text);
15987     while (*text == '\n') text++;
15988     len = strlen(text);
15989     while (len > 0 && text[len - 1] == '\n') len--;
15990     text[len] = NULLCHAR;
15991
15992     if (len == 0) return;
15993
15994     if (commentList[index] != NULL) {
15995       Boolean addClosingBrace = addBraces;
15996         old = commentList[index];
15997         oldlen = strlen(old);
15998         while(commentList[index][oldlen-1] ==  '\n')
15999           commentList[index][--oldlen] = NULLCHAR;
16000         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16001         safeStrCpy(commentList[index], old, oldlen + len + 6);
16002         free(old);
16003         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16004         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16005           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16006           while (*text == '\n') { text++; len--; }
16007           commentList[index][--oldlen] = NULLCHAR;
16008       }
16009         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16010         else          strcat(commentList[index], "\n");
16011         strcat(commentList[index], text);
16012         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16013         else          strcat(commentList[index], "\n");
16014     } else {
16015         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16016         if(addBraces)
16017           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16018         else commentList[index][0] = NULLCHAR;
16019         strcat(commentList[index], text);
16020         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16021         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16022     }
16023 }
16024
16025 static char *
16026 FindStr (char * text, char * sub_text)
16027 {
16028     char * result = strstr( text, sub_text );
16029
16030     if( result != NULL ) {
16031         result += strlen( sub_text );
16032     }
16033
16034     return result;
16035 }
16036
16037 /* [AS] Try to extract PV info from PGN comment */
16038 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16039 char *
16040 GetInfoFromComment (int index, char * text)
16041 {
16042     char * sep = text, *p;
16043
16044     if( text != NULL && index > 0 ) {
16045         int score = 0;
16046         int depth = 0;
16047         int time = -1, sec = 0, deci;
16048         char * s_eval = FindStr( text, "[%eval " );
16049         char * s_emt = FindStr( text, "[%emt " );
16050 #if 0
16051         if( s_eval != NULL || s_emt != NULL ) {
16052 #else
16053         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16054 #endif
16055             /* New style */
16056             char delim;
16057
16058             if( s_eval != NULL ) {
16059                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16060                     return text;
16061                 }
16062
16063                 if( delim != ']' ) {
16064                     return text;
16065                 }
16066             }
16067
16068             if( s_emt != NULL ) {
16069             }
16070                 return text;
16071         }
16072         else {
16073             /* We expect something like: [+|-]nnn.nn/dd */
16074             int score_lo = 0;
16075
16076             if(*text != '{') return text; // [HGM] braces: must be normal comment
16077
16078             sep = strchr( text, '/' );
16079             if( sep == NULL || sep < (text+4) ) {
16080                 return text;
16081             }
16082
16083             p = text;
16084             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16085             if(p[1] == '(') { // comment starts with PV
16086                p = strchr(p, ')'); // locate end of PV
16087                if(p == NULL || sep < p+5) return text;
16088                // at this point we have something like "{(.*) +0.23/6 ..."
16089                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16090                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16091                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16092             }
16093             time = -1; sec = -1; deci = -1;
16094             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16095                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16096                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16097                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16098                 return text;
16099             }
16100
16101             if( score_lo < 0 || score_lo >= 100 ) {
16102                 return text;
16103             }
16104
16105             if(sec >= 0) time = 600*time + 10*sec; else
16106             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16107
16108             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16109
16110             /* [HGM] PV time: now locate end of PV info */
16111             while( *++sep >= '0' && *sep <= '9'); // strip depth
16112             if(time >= 0)
16113             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16114             if(sec >= 0)
16115             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16116             if(deci >= 0)
16117             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16118             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16119         }
16120
16121         if( depth <= 0 ) {
16122             return text;
16123         }
16124
16125         if( time < 0 ) {
16126             time = -1;
16127         }
16128
16129         pvInfoList[index-1].depth = depth;
16130         pvInfoList[index-1].score = score;
16131         pvInfoList[index-1].time  = 10*time; // centi-sec
16132         if(*sep == '}') *sep = 0; else *--sep = '{';
16133         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16134     }
16135     return sep;
16136 }
16137
16138 void
16139 SendToProgram (char *message, ChessProgramState *cps)
16140 {
16141     int count, outCount, error;
16142     char buf[MSG_SIZ];
16143
16144     if (cps->pr == NoProc) return;
16145     Attention(cps);
16146
16147     if (appData.debugMode) {
16148         TimeMark now;
16149         GetTimeMark(&now);
16150         fprintf(debugFP, "%ld >%-6s: %s",
16151                 SubtractTimeMarks(&now, &programStartTime),
16152                 cps->which, message);
16153         if(serverFP)
16154             fprintf(serverFP, "%ld >%-6s: %s",
16155                 SubtractTimeMarks(&now, &programStartTime),
16156                 cps->which, message), fflush(serverFP);
16157     }
16158
16159     count = strlen(message);
16160     outCount = OutputToProcess(cps->pr, message, count, &error);
16161     if (outCount < count && !exiting
16162                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16163       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16164       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16165         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16166             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16167                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16168                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16169                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16170             } else {
16171                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16172                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16173                 gameInfo.result = res;
16174             }
16175             gameInfo.resultDetails = StrSave(buf);
16176         }
16177         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16178         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16179     }
16180 }
16181
16182 void
16183 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16184 {
16185     char *end_str;
16186     char buf[MSG_SIZ];
16187     ChessProgramState *cps = (ChessProgramState *)closure;
16188
16189     if (isr != cps->isr) return; /* Killed intentionally */
16190     if (count <= 0) {
16191         if (count == 0) {
16192             RemoveInputSource(cps->isr);
16193             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16194                     _(cps->which), cps->program);
16195             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16196             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16197                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16198                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16199                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16200                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16201                 } else {
16202                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16203                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16204                     gameInfo.result = res;
16205                 }
16206                 gameInfo.resultDetails = StrSave(buf);
16207             }
16208             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16209             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16210         } else {
16211             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16212                     _(cps->which), cps->program);
16213             RemoveInputSource(cps->isr);
16214
16215             /* [AS] Program is misbehaving badly... kill it */
16216             if( count == -2 ) {
16217                 DestroyChildProcess( cps->pr, 9 );
16218                 cps->pr = NoProc;
16219             }
16220
16221             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16222         }
16223         return;
16224     }
16225
16226     if ((end_str = strchr(message, '\r')) != NULL)
16227       *end_str = NULLCHAR;
16228     if ((end_str = strchr(message, '\n')) != NULL)
16229       *end_str = NULLCHAR;
16230
16231     if (appData.debugMode) {
16232         TimeMark now; int print = 1;
16233         char *quote = ""; char c; int i;
16234
16235         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16236                 char start = message[0];
16237                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16238                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16239                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16240                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16241                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16242                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16243                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16244                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16245                    sscanf(message, "hint: %c", &c)!=1 &&
16246                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16247                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16248                     print = (appData.engineComments >= 2);
16249                 }
16250                 message[0] = start; // restore original message
16251         }
16252         if(print) {
16253                 GetTimeMark(&now);
16254                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16255                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16256                         quote,
16257                         message);
16258                 if(serverFP)
16259                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16260                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16261                         quote,
16262                         message), fflush(serverFP);
16263         }
16264     }
16265
16266     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16267     if (appData.icsEngineAnalyze) {
16268         if (strstr(message, "whisper") != NULL ||
16269              strstr(message, "kibitz") != NULL ||
16270             strstr(message, "tellics") != NULL) return;
16271     }
16272
16273     HandleMachineMove(message, cps);
16274 }
16275
16276
16277 void
16278 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16279 {
16280     char buf[MSG_SIZ];
16281     int seconds;
16282
16283     if( timeControl_2 > 0 ) {
16284         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16285             tc = timeControl_2;
16286         }
16287     }
16288     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16289     inc /= cps->timeOdds;
16290     st  /= cps->timeOdds;
16291
16292     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16293
16294     if (st > 0) {
16295       /* Set exact time per move, normally using st command */
16296       if (cps->stKludge) {
16297         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16298         seconds = st % 60;
16299         if (seconds == 0) {
16300           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16301         } else {
16302           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16303         }
16304       } else {
16305         snprintf(buf, MSG_SIZ, "st %d\n", st);
16306       }
16307     } else {
16308       /* Set conventional or incremental time control, using level command */
16309       if (seconds == 0) {
16310         /* Note old gnuchess bug -- minutes:seconds used to not work.
16311            Fixed in later versions, but still avoid :seconds
16312            when seconds is 0. */
16313         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16314       } else {
16315         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16316                  seconds, inc/1000.);
16317       }
16318     }
16319     SendToProgram(buf, cps);
16320
16321     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16322     /* Orthogonally, limit search to given depth */
16323     if (sd > 0) {
16324       if (cps->sdKludge) {
16325         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16326       } else {
16327         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16328       }
16329       SendToProgram(buf, cps);
16330     }
16331
16332     if(cps->nps >= 0) { /* [HGM] nps */
16333         if(cps->supportsNPS == FALSE)
16334           cps->nps = -1; // don't use if engine explicitly says not supported!
16335         else {
16336           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16337           SendToProgram(buf, cps);
16338         }
16339     }
16340 }
16341
16342 ChessProgramState *
16343 WhitePlayer ()
16344 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16345 {
16346     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16347        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16348         return &second;
16349     return &first;
16350 }
16351
16352 void
16353 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16354 {
16355     char message[MSG_SIZ];
16356     long time, otime;
16357
16358     /* Note: this routine must be called when the clocks are stopped
16359        or when they have *just* been set or switched; otherwise
16360        it will be off by the time since the current tick started.
16361     */
16362     if (machineWhite) {
16363         time = whiteTimeRemaining / 10;
16364         otime = blackTimeRemaining / 10;
16365     } else {
16366         time = blackTimeRemaining / 10;
16367         otime = whiteTimeRemaining / 10;
16368     }
16369     /* [HGM] translate opponent's time by time-odds factor */
16370     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16371
16372     if (time <= 0) time = 1;
16373     if (otime <= 0) otime = 1;
16374
16375     snprintf(message, MSG_SIZ, "time %ld\n", time);
16376     SendToProgram(message, cps);
16377
16378     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16379     SendToProgram(message, cps);
16380 }
16381
16382 char *
16383 EngineDefinedVariant (ChessProgramState *cps, int n)
16384 {   // return name of n-th unknown variant that engine supports
16385     static char buf[MSG_SIZ];
16386     char *p, *s = cps->variants;
16387     if(!s) return NULL;
16388     do { // parse string from variants feature
16389       VariantClass v;
16390         p = strchr(s, ',');
16391         if(p) *p = NULLCHAR;
16392       v = StringToVariant(s);
16393       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16394         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16395             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16396         }
16397         if(p) *p++ = ',';
16398         if(n < 0) return buf;
16399     } while(s = p);
16400     return NULL;
16401 }
16402
16403 int
16404 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16405 {
16406   char buf[MSG_SIZ];
16407   int len = strlen(name);
16408   int val;
16409
16410   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16411     (*p) += len + 1;
16412     sscanf(*p, "%d", &val);
16413     *loc = (val != 0);
16414     while (**p && **p != ' ')
16415       (*p)++;
16416     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16417     SendToProgram(buf, cps);
16418     return TRUE;
16419   }
16420   return FALSE;
16421 }
16422
16423 int
16424 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16425 {
16426   char buf[MSG_SIZ];
16427   int len = strlen(name);
16428   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16429     (*p) += len + 1;
16430     sscanf(*p, "%d", loc);
16431     while (**p && **p != ' ') (*p)++;
16432     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16433     SendToProgram(buf, cps);
16434     return TRUE;
16435   }
16436   return FALSE;
16437 }
16438
16439 int
16440 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16441 {
16442   char buf[MSG_SIZ];
16443   int len = strlen(name);
16444   if (strncmp((*p), name, len) == 0
16445       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16446     (*p) += len + 2;
16447     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16448     sscanf(*p, "%[^\"]", *loc);
16449     while (**p && **p != '\"') (*p)++;
16450     if (**p == '\"') (*p)++;
16451     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16452     SendToProgram(buf, cps);
16453     return TRUE;
16454   }
16455   return FALSE;
16456 }
16457
16458 int
16459 ParseOption (Option *opt, ChessProgramState *cps)
16460 // [HGM] options: process the string that defines an engine option, and determine
16461 // name, type, default value, and allowed value range
16462 {
16463         char *p, *q, buf[MSG_SIZ];
16464         int n, min = (-1)<<31, max = 1<<31, def;
16465
16466         if(p = strstr(opt->name, " -spin ")) {
16467             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16468             if(max < min) max = min; // enforce consistency
16469             if(def < min) def = min;
16470             if(def > max) def = max;
16471             opt->value = def;
16472             opt->min = min;
16473             opt->max = max;
16474             opt->type = Spin;
16475         } else if((p = strstr(opt->name, " -slider "))) {
16476             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16477             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16478             if(max < min) max = min; // enforce consistency
16479             if(def < min) def = min;
16480             if(def > max) def = max;
16481             opt->value = def;
16482             opt->min = min;
16483             opt->max = max;
16484             opt->type = Spin; // Slider;
16485         } else if((p = strstr(opt->name, " -string "))) {
16486             opt->textValue = p+9;
16487             opt->type = TextBox;
16488         } else if((p = strstr(opt->name, " -file "))) {
16489             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16490             opt->textValue = p+7;
16491             opt->type = FileName; // FileName;
16492         } else if((p = strstr(opt->name, " -path "))) {
16493             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16494             opt->textValue = p+7;
16495             opt->type = PathName; // PathName;
16496         } else if(p = strstr(opt->name, " -check ")) {
16497             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16498             opt->value = (def != 0);
16499             opt->type = CheckBox;
16500         } else if(p = strstr(opt->name, " -combo ")) {
16501             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16502             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16503             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16504             opt->value = n = 0;
16505             while(q = StrStr(q, " /// ")) {
16506                 n++; *q = 0;    // count choices, and null-terminate each of them
16507                 q += 5;
16508                 if(*q == '*') { // remember default, which is marked with * prefix
16509                     q++;
16510                     opt->value = n;
16511                 }
16512                 cps->comboList[cps->comboCnt++] = q;
16513             }
16514             cps->comboList[cps->comboCnt++] = NULL;
16515             opt->max = n + 1;
16516             opt->type = ComboBox;
16517         } else if(p = strstr(opt->name, " -button")) {
16518             opt->type = Button;
16519         } else if(p = strstr(opt->name, " -save")) {
16520             opt->type = SaveButton;
16521         } else return FALSE;
16522         *p = 0; // terminate option name
16523         // now look if the command-line options define a setting for this engine option.
16524         if(cps->optionSettings && cps->optionSettings[0])
16525             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16526         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16527           snprintf(buf, MSG_SIZ, "option %s", p);
16528                 if(p = strstr(buf, ",")) *p = 0;
16529                 if(q = strchr(buf, '=')) switch(opt->type) {
16530                     case ComboBox:
16531                         for(n=0; n<opt->max; n++)
16532                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16533                         break;
16534                     case TextBox:
16535                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16536                         break;
16537                     case Spin:
16538                     case CheckBox:
16539                         opt->value = atoi(q+1);
16540                     default:
16541                         break;
16542                 }
16543                 strcat(buf, "\n");
16544                 SendToProgram(buf, cps);
16545         }
16546         return TRUE;
16547 }
16548
16549 void
16550 FeatureDone (ChessProgramState *cps, int val)
16551 {
16552   DelayedEventCallback cb = GetDelayedEvent();
16553   if ((cb == InitBackEnd3 && cps == &first) ||
16554       (cb == SettingsMenuIfReady && cps == &second) ||
16555       (cb == LoadEngine) ||
16556       (cb == TwoMachinesEventIfReady)) {
16557     CancelDelayedEvent();
16558     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16559   }
16560   cps->initDone = val;
16561   if(val) cps->reload = FALSE;
16562 }
16563
16564 /* Parse feature command from engine */
16565 void
16566 ParseFeatures (char *args, ChessProgramState *cps)
16567 {
16568   char *p = args;
16569   char *q = NULL;
16570   int val;
16571   char buf[MSG_SIZ];
16572
16573   for (;;) {
16574     while (*p == ' ') p++;
16575     if (*p == NULLCHAR) return;
16576
16577     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16578     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16579     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16580     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16581     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16582     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16583     if (BoolFeature(&p, "reuse", &val, cps)) {
16584       /* Engine can disable reuse, but can't enable it if user said no */
16585       if (!val) cps->reuse = FALSE;
16586       continue;
16587     }
16588     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16589     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16590       if (gameMode == TwoMachinesPlay) {
16591         DisplayTwoMachinesTitle();
16592       } else {
16593         DisplayTitle("");
16594       }
16595       continue;
16596     }
16597     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16598     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16599     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16600     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16601     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16602     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16603     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16604     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16605     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16606     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16607     if (IntFeature(&p, "done", &val, cps)) {
16608       FeatureDone(cps, val);
16609       continue;
16610     }
16611     /* Added by Tord: */
16612     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16613     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16614     /* End of additions by Tord */
16615
16616     /* [HGM] added features: */
16617     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16618     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16619     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16620     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16621     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16622     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16623     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16624     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16625         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16626         FREE(cps->option[cps->nrOptions].name);
16627         cps->option[cps->nrOptions].name = q; q = NULL;
16628         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16629           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16630             SendToProgram(buf, cps);
16631             continue;
16632         }
16633         if(cps->nrOptions >= MAX_OPTIONS) {
16634             cps->nrOptions--;
16635             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16636             DisplayError(buf, 0);
16637         }
16638         continue;
16639     }
16640     /* End of additions by HGM */
16641
16642     /* unknown feature: complain and skip */
16643     q = p;
16644     while (*q && *q != '=') q++;
16645     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16646     SendToProgram(buf, cps);
16647     p = q;
16648     if (*p == '=') {
16649       p++;
16650       if (*p == '\"') {
16651         p++;
16652         while (*p && *p != '\"') p++;
16653         if (*p == '\"') p++;
16654       } else {
16655         while (*p && *p != ' ') p++;
16656       }
16657     }
16658   }
16659
16660 }
16661
16662 void
16663 PeriodicUpdatesEvent (int newState)
16664 {
16665     if (newState == appData.periodicUpdates)
16666       return;
16667
16668     appData.periodicUpdates=newState;
16669
16670     /* Display type changes, so update it now */
16671 //    DisplayAnalysis();
16672
16673     /* Get the ball rolling again... */
16674     if (newState) {
16675         AnalysisPeriodicEvent(1);
16676         StartAnalysisClock();
16677     }
16678 }
16679
16680 void
16681 PonderNextMoveEvent (int newState)
16682 {
16683     if (newState == appData.ponderNextMove) return;
16684     if (gameMode == EditPosition) EditPositionDone(TRUE);
16685     if (newState) {
16686         SendToProgram("hard\n", &first);
16687         if (gameMode == TwoMachinesPlay) {
16688             SendToProgram("hard\n", &second);
16689         }
16690     } else {
16691         SendToProgram("easy\n", &first);
16692         thinkOutput[0] = NULLCHAR;
16693         if (gameMode == TwoMachinesPlay) {
16694             SendToProgram("easy\n", &second);
16695         }
16696     }
16697     appData.ponderNextMove = newState;
16698 }
16699
16700 void
16701 NewSettingEvent (int option, int *feature, char *command, int value)
16702 {
16703     char buf[MSG_SIZ];
16704
16705     if (gameMode == EditPosition) EditPositionDone(TRUE);
16706     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16707     if(feature == NULL || *feature) SendToProgram(buf, &first);
16708     if (gameMode == TwoMachinesPlay) {
16709         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16710     }
16711 }
16712
16713 void
16714 ShowThinkingEvent ()
16715 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16716 {
16717     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16718     int newState = appData.showThinking
16719         // [HGM] thinking: other features now need thinking output as well
16720         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16721
16722     if (oldState == newState) return;
16723     oldState = newState;
16724     if (gameMode == EditPosition) EditPositionDone(TRUE);
16725     if (oldState) {
16726         SendToProgram("post\n", &first);
16727         if (gameMode == TwoMachinesPlay) {
16728             SendToProgram("post\n", &second);
16729         }
16730     } else {
16731         SendToProgram("nopost\n", &first);
16732         thinkOutput[0] = NULLCHAR;
16733         if (gameMode == TwoMachinesPlay) {
16734             SendToProgram("nopost\n", &second);
16735         }
16736     }
16737 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16738 }
16739
16740 void
16741 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16742 {
16743   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16744   if (pr == NoProc) return;
16745   AskQuestion(title, question, replyPrefix, pr);
16746 }
16747
16748 void
16749 TypeInEvent (char firstChar)
16750 {
16751     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16752         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16753         gameMode == AnalyzeMode || gameMode == EditGame ||
16754         gameMode == EditPosition || gameMode == IcsExamining ||
16755         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16756         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16757                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16758                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16759         gameMode == Training) PopUpMoveDialog(firstChar);
16760 }
16761
16762 void
16763 TypeInDoneEvent (char *move)
16764 {
16765         Board board;
16766         int n, fromX, fromY, toX, toY;
16767         char promoChar;
16768         ChessMove moveType;
16769
16770         // [HGM] FENedit
16771         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16772                 EditPositionPasteFEN(move);
16773                 return;
16774         }
16775         // [HGM] movenum: allow move number to be typed in any mode
16776         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16777           ToNrEvent(2*n-1);
16778           return;
16779         }
16780         // undocumented kludge: allow command-line option to be typed in!
16781         // (potentially fatal, and does not implement the effect of the option.)
16782         // should only be used for options that are values on which future decisions will be made,
16783         // and definitely not on options that would be used during initialization.
16784         if(strstr(move, "!!! -") == move) {
16785             ParseArgsFromString(move+4);
16786             return;
16787         }
16788
16789       if (gameMode != EditGame && currentMove != forwardMostMove &&
16790         gameMode != Training) {
16791         DisplayMoveError(_("Displayed move is not current"));
16792       } else {
16793         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16794           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16795         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16796         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16797           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16798           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16799         } else {
16800           DisplayMoveError(_("Could not parse move"));
16801         }
16802       }
16803 }
16804
16805 void
16806 DisplayMove (int moveNumber)
16807 {
16808     char message[MSG_SIZ];
16809     char res[MSG_SIZ];
16810     char cpThinkOutput[MSG_SIZ];
16811
16812     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16813
16814     if (moveNumber == forwardMostMove - 1 ||
16815         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16816
16817         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16818
16819         if (strchr(cpThinkOutput, '\n')) {
16820             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16821         }
16822     } else {
16823         *cpThinkOutput = NULLCHAR;
16824     }
16825
16826     /* [AS] Hide thinking from human user */
16827     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16828         *cpThinkOutput = NULLCHAR;
16829         if( thinkOutput[0] != NULLCHAR ) {
16830             int i;
16831
16832             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16833                 cpThinkOutput[i] = '.';
16834             }
16835             cpThinkOutput[i] = NULLCHAR;
16836             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16837         }
16838     }
16839
16840     if (moveNumber == forwardMostMove - 1 &&
16841         gameInfo.resultDetails != NULL) {
16842         if (gameInfo.resultDetails[0] == NULLCHAR) {
16843           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16844         } else {
16845           snprintf(res, MSG_SIZ, " {%s} %s",
16846                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16847         }
16848     } else {
16849         res[0] = NULLCHAR;
16850     }
16851
16852     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16853         DisplayMessage(res, cpThinkOutput);
16854     } else {
16855       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16856                 WhiteOnMove(moveNumber) ? " " : ".. ",
16857                 parseList[moveNumber], res);
16858         DisplayMessage(message, cpThinkOutput);
16859     }
16860 }
16861
16862 void
16863 DisplayComment (int moveNumber, char *text)
16864 {
16865     char title[MSG_SIZ];
16866
16867     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16868       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16869     } else {
16870       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16871               WhiteOnMove(moveNumber) ? " " : ".. ",
16872               parseList[moveNumber]);
16873     }
16874     if (text != NULL && (appData.autoDisplayComment || commentUp))
16875         CommentPopUp(title, text);
16876 }
16877
16878 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16879  * might be busy thinking or pondering.  It can be omitted if your
16880  * gnuchess is configured to stop thinking immediately on any user
16881  * input.  However, that gnuchess feature depends on the FIONREAD
16882  * ioctl, which does not work properly on some flavors of Unix.
16883  */
16884 void
16885 Attention (ChessProgramState *cps)
16886 {
16887 #if ATTENTION
16888     if (!cps->useSigint) return;
16889     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16890     switch (gameMode) {
16891       case MachinePlaysWhite:
16892       case MachinePlaysBlack:
16893       case TwoMachinesPlay:
16894       case IcsPlayingWhite:
16895       case IcsPlayingBlack:
16896       case AnalyzeMode:
16897       case AnalyzeFile:
16898         /* Skip if we know it isn't thinking */
16899         if (!cps->maybeThinking) return;
16900         if (appData.debugMode)
16901           fprintf(debugFP, "Interrupting %s\n", cps->which);
16902         InterruptChildProcess(cps->pr);
16903         cps->maybeThinking = FALSE;
16904         break;
16905       default:
16906         break;
16907     }
16908 #endif /*ATTENTION*/
16909 }
16910
16911 int
16912 CheckFlags ()
16913 {
16914     if (whiteTimeRemaining <= 0) {
16915         if (!whiteFlag) {
16916             whiteFlag = TRUE;
16917             if (appData.icsActive) {
16918                 if (appData.autoCallFlag &&
16919                     gameMode == IcsPlayingBlack && !blackFlag) {
16920                   SendToICS(ics_prefix);
16921                   SendToICS("flag\n");
16922                 }
16923             } else {
16924                 if (blackFlag) {
16925                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16926                 } else {
16927                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16928                     if (appData.autoCallFlag) {
16929                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16930                         return TRUE;
16931                     }
16932                 }
16933             }
16934         }
16935     }
16936     if (blackTimeRemaining <= 0) {
16937         if (!blackFlag) {
16938             blackFlag = TRUE;
16939             if (appData.icsActive) {
16940                 if (appData.autoCallFlag &&
16941                     gameMode == IcsPlayingWhite && !whiteFlag) {
16942                   SendToICS(ics_prefix);
16943                   SendToICS("flag\n");
16944                 }
16945             } else {
16946                 if (whiteFlag) {
16947                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16948                 } else {
16949                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16950                     if (appData.autoCallFlag) {
16951                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16952                         return TRUE;
16953                     }
16954                 }
16955             }
16956         }
16957     }
16958     return FALSE;
16959 }
16960
16961 void
16962 CheckTimeControl ()
16963 {
16964     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16965         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16966
16967     /*
16968      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16969      */
16970     if ( !WhiteOnMove(forwardMostMove) ) {
16971         /* White made time control */
16972         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16973         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16974         /* [HGM] time odds: correct new time quota for time odds! */
16975                                             / WhitePlayer()->timeOdds;
16976         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16977     } else {
16978         lastBlack -= blackTimeRemaining;
16979         /* Black made time control */
16980         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16981                                             / WhitePlayer()->other->timeOdds;
16982         lastWhite = whiteTimeRemaining;
16983     }
16984 }
16985
16986 void
16987 DisplayBothClocks ()
16988 {
16989     int wom = gameMode == EditPosition ?
16990       !blackPlaysFirst : WhiteOnMove(currentMove);
16991     DisplayWhiteClock(whiteTimeRemaining, wom);
16992     DisplayBlackClock(blackTimeRemaining, !wom);
16993 }
16994
16995
16996 /* Timekeeping seems to be a portability nightmare.  I think everyone
16997    has ftime(), but I'm really not sure, so I'm including some ifdefs
16998    to use other calls if you don't.  Clocks will be less accurate if
16999    you have neither ftime nor gettimeofday.
17000 */
17001
17002 /* VS 2008 requires the #include outside of the function */
17003 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17004 #include <sys/timeb.h>
17005 #endif
17006
17007 /* Get the current time as a TimeMark */
17008 void
17009 GetTimeMark (TimeMark *tm)
17010 {
17011 #if HAVE_GETTIMEOFDAY
17012
17013     struct timeval timeVal;
17014     struct timezone timeZone;
17015
17016     gettimeofday(&timeVal, &timeZone);
17017     tm->sec = (long) timeVal.tv_sec;
17018     tm->ms = (int) (timeVal.tv_usec / 1000L);
17019
17020 #else /*!HAVE_GETTIMEOFDAY*/
17021 #if HAVE_FTIME
17022
17023 // include <sys/timeb.h> / moved to just above start of function
17024     struct timeb timeB;
17025
17026     ftime(&timeB);
17027     tm->sec = (long) timeB.time;
17028     tm->ms = (int) timeB.millitm;
17029
17030 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17031     tm->sec = (long) time(NULL);
17032     tm->ms = 0;
17033 #endif
17034 #endif
17035 }
17036
17037 /* Return the difference in milliseconds between two
17038    time marks.  We assume the difference will fit in a long!
17039 */
17040 long
17041 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17042 {
17043     return 1000L*(tm2->sec - tm1->sec) +
17044            (long) (tm2->ms - tm1->ms);
17045 }
17046
17047
17048 /*
17049  * Code to manage the game clocks.
17050  *
17051  * In tournament play, black starts the clock and then white makes a move.
17052  * We give the human user a slight advantage if he is playing white---the
17053  * clocks don't run until he makes his first move, so it takes zero time.
17054  * Also, we don't account for network lag, so we could get out of sync
17055  * with GNU Chess's clock -- but then, referees are always right.
17056  */
17057
17058 static TimeMark tickStartTM;
17059 static long intendedTickLength;
17060
17061 long
17062 NextTickLength (long timeRemaining)
17063 {
17064     long nominalTickLength, nextTickLength;
17065
17066     if (timeRemaining > 0L && timeRemaining <= 10000L)
17067       nominalTickLength = 100L;
17068     else
17069       nominalTickLength = 1000L;
17070     nextTickLength = timeRemaining % nominalTickLength;
17071     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17072
17073     return nextTickLength;
17074 }
17075
17076 /* Adjust clock one minute up or down */
17077 void
17078 AdjustClock (Boolean which, int dir)
17079 {
17080     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17081     if(which) blackTimeRemaining += 60000*dir;
17082     else      whiteTimeRemaining += 60000*dir;
17083     DisplayBothClocks();
17084     adjustedClock = TRUE;
17085 }
17086
17087 /* Stop clocks and reset to a fresh time control */
17088 void
17089 ResetClocks ()
17090 {
17091     (void) StopClockTimer();
17092     if (appData.icsActive) {
17093         whiteTimeRemaining = blackTimeRemaining = 0;
17094     } else if (searchTime) {
17095         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17096         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17097     } else { /* [HGM] correct new time quote for time odds */
17098         whiteTC = blackTC = fullTimeControlString;
17099         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17100         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17101     }
17102     if (whiteFlag || blackFlag) {
17103         DisplayTitle("");
17104         whiteFlag = blackFlag = FALSE;
17105     }
17106     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17107     DisplayBothClocks();
17108     adjustedClock = FALSE;
17109 }
17110
17111 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17112
17113 /* Decrement running clock by amount of time that has passed */
17114 void
17115 DecrementClocks ()
17116 {
17117     long timeRemaining;
17118     long lastTickLength, fudge;
17119     TimeMark now;
17120
17121     if (!appData.clockMode) return;
17122     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17123
17124     GetTimeMark(&now);
17125
17126     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17127
17128     /* Fudge if we woke up a little too soon */
17129     fudge = intendedTickLength - lastTickLength;
17130     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17131
17132     if (WhiteOnMove(forwardMostMove)) {
17133         if(whiteNPS >= 0) lastTickLength = 0;
17134         timeRemaining = whiteTimeRemaining -= lastTickLength;
17135         if(timeRemaining < 0 && !appData.icsActive) {
17136             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17137             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17138                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17139                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17140             }
17141         }
17142         DisplayWhiteClock(whiteTimeRemaining - fudge,
17143                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17144     } else {
17145         if(blackNPS >= 0) lastTickLength = 0;
17146         timeRemaining = blackTimeRemaining -= lastTickLength;
17147         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17148             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17149             if(suddenDeath) {
17150                 blackStartMove = forwardMostMove;
17151                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17152             }
17153         }
17154         DisplayBlackClock(blackTimeRemaining - fudge,
17155                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17156     }
17157     if (CheckFlags()) return;
17158
17159     if(twoBoards) { // count down secondary board's clocks as well
17160         activePartnerTime -= lastTickLength;
17161         partnerUp = 1;
17162         if(activePartner == 'W')
17163             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17164         else
17165             DisplayBlackClock(activePartnerTime, TRUE);
17166         partnerUp = 0;
17167     }
17168
17169     tickStartTM = now;
17170     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17171     StartClockTimer(intendedTickLength);
17172
17173     /* if the time remaining has fallen below the alarm threshold, sound the
17174      * alarm. if the alarm has sounded and (due to a takeback or time control
17175      * with increment) the time remaining has increased to a level above the
17176      * threshold, reset the alarm so it can sound again.
17177      */
17178
17179     if (appData.icsActive && appData.icsAlarm) {
17180
17181         /* make sure we are dealing with the user's clock */
17182         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17183                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17184            )) return;
17185
17186         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17187             alarmSounded = FALSE;
17188         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17189             PlayAlarmSound();
17190             alarmSounded = TRUE;
17191         }
17192     }
17193 }
17194
17195
17196 /* A player has just moved, so stop the previously running
17197    clock and (if in clock mode) start the other one.
17198    We redisplay both clocks in case we're in ICS mode, because
17199    ICS gives us an update to both clocks after every move.
17200    Note that this routine is called *after* forwardMostMove
17201    is updated, so the last fractional tick must be subtracted
17202    from the color that is *not* on move now.
17203 */
17204 void
17205 SwitchClocks (int newMoveNr)
17206 {
17207     long lastTickLength;
17208     TimeMark now;
17209     int flagged = FALSE;
17210
17211     GetTimeMark(&now);
17212
17213     if (StopClockTimer() && appData.clockMode) {
17214         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17215         if (!WhiteOnMove(forwardMostMove)) {
17216             if(blackNPS >= 0) lastTickLength = 0;
17217             blackTimeRemaining -= lastTickLength;
17218            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17219 //         if(pvInfoList[forwardMostMove].time == -1)
17220                  pvInfoList[forwardMostMove].time =               // use GUI time
17221                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17222         } else {
17223            if(whiteNPS >= 0) lastTickLength = 0;
17224            whiteTimeRemaining -= lastTickLength;
17225            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17226 //         if(pvInfoList[forwardMostMove].time == -1)
17227                  pvInfoList[forwardMostMove].time =
17228                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17229         }
17230         flagged = CheckFlags();
17231     }
17232     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17233     CheckTimeControl();
17234
17235     if (flagged || !appData.clockMode) return;
17236
17237     switch (gameMode) {
17238       case MachinePlaysBlack:
17239       case MachinePlaysWhite:
17240       case BeginningOfGame:
17241         if (pausing) return;
17242         break;
17243
17244       case EditGame:
17245       case PlayFromGameFile:
17246       case IcsExamining:
17247         return;
17248
17249       default:
17250         break;
17251     }
17252
17253     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17254         if(WhiteOnMove(forwardMostMove))
17255              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17256         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17257     }
17258
17259     tickStartTM = now;
17260     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17261       whiteTimeRemaining : blackTimeRemaining);
17262     StartClockTimer(intendedTickLength);
17263 }
17264
17265
17266 /* Stop both clocks */
17267 void
17268 StopClocks ()
17269 {
17270     long lastTickLength;
17271     TimeMark now;
17272
17273     if (!StopClockTimer()) return;
17274     if (!appData.clockMode) return;
17275
17276     GetTimeMark(&now);
17277
17278     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17279     if (WhiteOnMove(forwardMostMove)) {
17280         if(whiteNPS >= 0) lastTickLength = 0;
17281         whiteTimeRemaining -= lastTickLength;
17282         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17283     } else {
17284         if(blackNPS >= 0) lastTickLength = 0;
17285         blackTimeRemaining -= lastTickLength;
17286         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17287     }
17288     CheckFlags();
17289 }
17290
17291 /* Start clock of player on move.  Time may have been reset, so
17292    if clock is already running, stop and restart it. */
17293 void
17294 StartClocks ()
17295 {
17296     (void) StopClockTimer(); /* in case it was running already */
17297     DisplayBothClocks();
17298     if (CheckFlags()) return;
17299
17300     if (!appData.clockMode) return;
17301     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17302
17303     GetTimeMark(&tickStartTM);
17304     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17305       whiteTimeRemaining : blackTimeRemaining);
17306
17307    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17308     whiteNPS = blackNPS = -1;
17309     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17310        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17311         whiteNPS = first.nps;
17312     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17313        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17314         blackNPS = first.nps;
17315     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17316         whiteNPS = second.nps;
17317     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17318         blackNPS = second.nps;
17319     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17320
17321     StartClockTimer(intendedTickLength);
17322 }
17323
17324 char *
17325 TimeString (long ms)
17326 {
17327     long second, minute, hour, day;
17328     char *sign = "";
17329     static char buf[32];
17330
17331     if (ms > 0 && ms <= 9900) {
17332       /* convert milliseconds to tenths, rounding up */
17333       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17334
17335       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17336       return buf;
17337     }
17338
17339     /* convert milliseconds to seconds, rounding up */
17340     /* use floating point to avoid strangeness of integer division
17341        with negative dividends on many machines */
17342     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17343
17344     if (second < 0) {
17345         sign = "-";
17346         second = -second;
17347     }
17348
17349     day = second / (60 * 60 * 24);
17350     second = second % (60 * 60 * 24);
17351     hour = second / (60 * 60);
17352     second = second % (60 * 60);
17353     minute = second / 60;
17354     second = second % 60;
17355
17356     if (day > 0)
17357       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17358               sign, day, hour, minute, second);
17359     else if (hour > 0)
17360       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17361     else
17362       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17363
17364     return buf;
17365 }
17366
17367
17368 /*
17369  * This is necessary because some C libraries aren't ANSI C compliant yet.
17370  */
17371 char *
17372 StrStr (char *string, char *match)
17373 {
17374     int i, length;
17375
17376     length = strlen(match);
17377
17378     for (i = strlen(string) - length; i >= 0; i--, string++)
17379       if (!strncmp(match, string, length))
17380         return string;
17381
17382     return NULL;
17383 }
17384
17385 char *
17386 StrCaseStr (char *string, char *match)
17387 {
17388     int i, j, length;
17389
17390     length = strlen(match);
17391
17392     for (i = strlen(string) - length; i >= 0; i--, string++) {
17393         for (j = 0; j < length; j++) {
17394             if (ToLower(match[j]) != ToLower(string[j]))
17395               break;
17396         }
17397         if (j == length) return string;
17398     }
17399
17400     return NULL;
17401 }
17402
17403 #ifndef _amigados
17404 int
17405 StrCaseCmp (char *s1, char *s2)
17406 {
17407     char c1, c2;
17408
17409     for (;;) {
17410         c1 = ToLower(*s1++);
17411         c2 = ToLower(*s2++);
17412         if (c1 > c2) return 1;
17413         if (c1 < c2) return -1;
17414         if (c1 == NULLCHAR) return 0;
17415     }
17416 }
17417
17418
17419 int
17420 ToLower (int c)
17421 {
17422     return isupper(c) ? tolower(c) : c;
17423 }
17424
17425
17426 int
17427 ToUpper (int c)
17428 {
17429     return islower(c) ? toupper(c) : c;
17430 }
17431 #endif /* !_amigados    */
17432
17433 char *
17434 StrSave (char *s)
17435 {
17436   char *ret;
17437
17438   if ((ret = (char *) malloc(strlen(s) + 1)))
17439     {
17440       safeStrCpy(ret, s, strlen(s)+1);
17441     }
17442   return ret;
17443 }
17444
17445 char *
17446 StrSavePtr (char *s, char **savePtr)
17447 {
17448     if (*savePtr) {
17449         free(*savePtr);
17450     }
17451     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17452       safeStrCpy(*savePtr, s, strlen(s)+1);
17453     }
17454     return(*savePtr);
17455 }
17456
17457 char *
17458 PGNDate ()
17459 {
17460     time_t clock;
17461     struct tm *tm;
17462     char buf[MSG_SIZ];
17463
17464     clock = time((time_t *)NULL);
17465     tm = localtime(&clock);
17466     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17467             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17468     return StrSave(buf);
17469 }
17470
17471
17472 char *
17473 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17474 {
17475     int i, j, fromX, fromY, toX, toY;
17476     int whiteToPlay;
17477     char buf[MSG_SIZ];
17478     char *p, *q;
17479     int emptycount;
17480     ChessSquare piece;
17481
17482     whiteToPlay = (gameMode == EditPosition) ?
17483       !blackPlaysFirst : (move % 2 == 0);
17484     p = buf;
17485
17486     /* Piece placement data */
17487     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17488         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17489         emptycount = 0;
17490         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17491             if (boards[move][i][j] == EmptySquare) {
17492                 emptycount++;
17493             } else { ChessSquare piece = boards[move][i][j];
17494                 if (emptycount > 0) {
17495                     if(emptycount<10) /* [HGM] can be >= 10 */
17496                         *p++ = '0' + emptycount;
17497                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17498                     emptycount = 0;
17499                 }
17500                 if(PieceToChar(piece) == '+') {
17501                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17502                     *p++ = '+';
17503                     piece = (ChessSquare)(DEMOTED piece);
17504                 }
17505                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17506                 if(p[-1] == '~') {
17507                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17508                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17509                     *p++ = '~';
17510                 }
17511             }
17512         }
17513         if (emptycount > 0) {
17514             if(emptycount<10) /* [HGM] can be >= 10 */
17515                 *p++ = '0' + emptycount;
17516             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17517             emptycount = 0;
17518         }
17519         *p++ = '/';
17520     }
17521     *(p - 1) = ' ';
17522
17523     /* [HGM] print Crazyhouse or Shogi holdings */
17524     if( gameInfo.holdingsWidth ) {
17525         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17526         q = p;
17527         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17528             piece = boards[move][i][BOARD_WIDTH-1];
17529             if( piece != EmptySquare )
17530               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17531                   *p++ = PieceToChar(piece);
17532         }
17533         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17534             piece = boards[move][BOARD_HEIGHT-i-1][0];
17535             if( piece != EmptySquare )
17536               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17537                   *p++ = PieceToChar(piece);
17538         }
17539
17540         if( q == p ) *p++ = '-';
17541         *p++ = ']';
17542         *p++ = ' ';
17543     }
17544
17545     /* Active color */
17546     *p++ = whiteToPlay ? 'w' : 'b';
17547     *p++ = ' ';
17548
17549   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17550     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17551   } else {
17552   if(nrCastlingRights) {
17553      q = p;
17554      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17555        /* [HGM] write directly from rights */
17556            if(boards[move][CASTLING][2] != NoRights &&
17557               boards[move][CASTLING][0] != NoRights   )
17558                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17559            if(boards[move][CASTLING][2] != NoRights &&
17560               boards[move][CASTLING][1] != NoRights   )
17561                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17562            if(boards[move][CASTLING][5] != NoRights &&
17563               boards[move][CASTLING][3] != NoRights   )
17564                 *p++ = boards[move][CASTLING][3] + AAA;
17565            if(boards[move][CASTLING][5] != NoRights &&
17566               boards[move][CASTLING][4] != NoRights   )
17567                 *p++ = boards[move][CASTLING][4] + AAA;
17568      } else {
17569
17570         /* [HGM] write true castling rights */
17571         if( nrCastlingRights == 6 ) {
17572             int q, k=0;
17573             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17574                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17575             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17576                  boards[move][CASTLING][2] != NoRights  );
17577             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17578                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17579                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17580                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17581                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17582             }
17583             if(q) *p++ = 'Q';
17584             k = 0;
17585             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17586                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17587             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17588                  boards[move][CASTLING][5] != NoRights  );
17589             if(gameInfo.variant == VariantSChess) {
17590                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17591                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17592                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17593                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17594             }
17595             if(q) *p++ = 'q';
17596         }
17597      }
17598      if (q == p) *p++ = '-'; /* No castling rights */
17599      *p++ = ' ';
17600   }
17601
17602   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17603      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17604      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17605     /* En passant target square */
17606     if (move > backwardMostMove) {
17607         fromX = moveList[move - 1][0] - AAA;
17608         fromY = moveList[move - 1][1] - ONE;
17609         toX = moveList[move - 1][2] - AAA;
17610         toY = moveList[move - 1][3] - ONE;
17611         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17612             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17613             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17614             fromX == toX) {
17615             /* 2-square pawn move just happened */
17616             *p++ = toX + AAA;
17617             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17618         } else {
17619             *p++ = '-';
17620         }
17621     } else if(move == backwardMostMove) {
17622         // [HGM] perhaps we should always do it like this, and forget the above?
17623         if((signed char)boards[move][EP_STATUS] >= 0) {
17624             *p++ = boards[move][EP_STATUS] + AAA;
17625             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17626         } else {
17627             *p++ = '-';
17628         }
17629     } else {
17630         *p++ = '-';
17631     }
17632     *p++ = ' ';
17633   }
17634   }
17635
17636     if(moveCounts)
17637     {   int i = 0, j=move;
17638
17639         /* [HGM] find reversible plies */
17640         if (appData.debugMode) { int k;
17641             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17642             for(k=backwardMostMove; k<=forwardMostMove; k++)
17643                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17644
17645         }
17646
17647         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17648         if( j == backwardMostMove ) i += initialRulePlies;
17649         sprintf(p, "%d ", i);
17650         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17651
17652         /* Fullmove number */
17653         sprintf(p, "%d", (move / 2) + 1);
17654     } else *--p = NULLCHAR;
17655
17656     return StrSave(buf);
17657 }
17658
17659 Boolean
17660 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17661 {
17662     int i, j, k, w=0;
17663     char *p, c;
17664     int emptycount, virgin[BOARD_FILES];
17665     ChessSquare piece;
17666
17667     p = fen;
17668
17669     /* Piece placement data */
17670     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17671         j = 0;
17672         for (;;) {
17673             if (*p == '/' || *p == ' ' || *p == '[' ) {
17674                 if(j > w) w = j;
17675                 emptycount = gameInfo.boardWidth - j;
17676                 while (emptycount--)
17677                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17678                 if (*p == '/') p++;
17679                 else if(autoSize) { // we stumbled unexpectedly into end of board
17680                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17681                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17682                     }
17683                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17684                 }
17685                 break;
17686 #if(BOARD_FILES >= 10)
17687             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17688                 p++; emptycount=10;
17689                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17690                 while (emptycount--)
17691                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17692 #endif
17693             } else if (*p == '*') {
17694                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17695             } else if (isdigit(*p)) {
17696                 emptycount = *p++ - '0';
17697                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17698                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17699                 while (emptycount--)
17700                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17701             } else if (*p == '+' || isalpha(*p)) {
17702                 if (j >= gameInfo.boardWidth) return FALSE;
17703                 if(*p=='+') {
17704                     piece = CharToPiece(*++p);
17705                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17706                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17707                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17708                 } else piece = CharToPiece(*p++);
17709
17710                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17711                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17712                     piece = (ChessSquare) (PROMOTED piece);
17713                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17714                     p++;
17715                 }
17716                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17717             } else {
17718                 return FALSE;
17719             }
17720         }
17721     }
17722     while (*p == '/' || *p == ' ') p++;
17723
17724     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17725
17726     /* [HGM] by default clear Crazyhouse holdings, if present */
17727     if(gameInfo.holdingsWidth) {
17728        for(i=0; i<BOARD_HEIGHT; i++) {
17729            board[i][0]             = EmptySquare; /* black holdings */
17730            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17731            board[i][1]             = (ChessSquare) 0; /* black counts */
17732            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17733        }
17734     }
17735
17736     /* [HGM] look for Crazyhouse holdings here */
17737     while(*p==' ') p++;
17738     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17739         if(*p == '[') p++;
17740         if(*p == '-' ) p++; /* empty holdings */ else {
17741             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17742             /* if we would allow FEN reading to set board size, we would   */
17743             /* have to add holdings and shift the board read so far here   */
17744             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17745                 p++;
17746                 if((int) piece >= (int) BlackPawn ) {
17747                     i = (int)piece - (int)BlackPawn;
17748                     i = PieceToNumber((ChessSquare)i);
17749                     if( i >= gameInfo.holdingsSize ) return FALSE;
17750                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17751                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17752                 } else {
17753                     i = (int)piece - (int)WhitePawn;
17754                     i = PieceToNumber((ChessSquare)i);
17755                     if( i >= gameInfo.holdingsSize ) return FALSE;
17756                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17757                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17758                 }
17759             }
17760         }
17761         if(*p == ']') p++;
17762     }
17763
17764     while(*p == ' ') p++;
17765
17766     /* Active color */
17767     c = *p++;
17768     if(appData.colorNickNames) {
17769       if( c == appData.colorNickNames[0] ) c = 'w'; else
17770       if( c == appData.colorNickNames[1] ) c = 'b';
17771     }
17772     switch (c) {
17773       case 'w':
17774         *blackPlaysFirst = FALSE;
17775         break;
17776       case 'b':
17777         *blackPlaysFirst = TRUE;
17778         break;
17779       default:
17780         return FALSE;
17781     }
17782
17783     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17784     /* return the extra info in global variiables             */
17785
17786     /* set defaults in case FEN is incomplete */
17787     board[EP_STATUS] = EP_UNKNOWN;
17788     for(i=0; i<nrCastlingRights; i++ ) {
17789         board[CASTLING][i] =
17790             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17791     }   /* assume possible unless obviously impossible */
17792     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17793     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17794     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17795                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17796     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17797     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17798     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17799                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17800     FENrulePlies = 0;
17801
17802     while(*p==' ') p++;
17803     if(nrCastlingRights) {
17804       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17805       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17806           /* castling indicator present, so default becomes no castlings */
17807           for(i=0; i<nrCastlingRights; i++ ) {
17808                  board[CASTLING][i] = NoRights;
17809           }
17810       }
17811       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17812              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17813              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17814              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17815         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17816
17817         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17818             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17819             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17820         }
17821         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17822             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17823         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17824                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17825         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17826                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17827         switch(c) {
17828           case'K':
17829               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17830               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17831               board[CASTLING][2] = whiteKingFile;
17832               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17833               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17834               break;
17835           case'Q':
17836               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17837               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17838               board[CASTLING][2] = whiteKingFile;
17839               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17840               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17841               break;
17842           case'k':
17843               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17844               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17845               board[CASTLING][5] = blackKingFile;
17846               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17847               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17848               break;
17849           case'q':
17850               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17851               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17852               board[CASTLING][5] = blackKingFile;
17853               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17854               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17855           case '-':
17856               break;
17857           default: /* FRC castlings */
17858               if(c >= 'a') { /* black rights */
17859                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17860                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17861                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17862                   if(i == BOARD_RGHT) break;
17863                   board[CASTLING][5] = i;
17864                   c -= AAA;
17865                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17866                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17867                   if(c > i)
17868                       board[CASTLING][3] = c;
17869                   else
17870                       board[CASTLING][4] = c;
17871               } else { /* white rights */
17872                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17873                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17874                     if(board[0][i] == WhiteKing) break;
17875                   if(i == BOARD_RGHT) break;
17876                   board[CASTLING][2] = i;
17877                   c -= AAA - 'a' + 'A';
17878                   if(board[0][c] >= WhiteKing) break;
17879                   if(c > i)
17880                       board[CASTLING][0] = c;
17881                   else
17882                       board[CASTLING][1] = c;
17883               }
17884         }
17885       }
17886       for(i=0; i<nrCastlingRights; i++)
17887         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17888       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17889     if (appData.debugMode) {
17890         fprintf(debugFP, "FEN castling rights:");
17891         for(i=0; i<nrCastlingRights; i++)
17892         fprintf(debugFP, " %d", board[CASTLING][i]);
17893         fprintf(debugFP, "\n");
17894     }
17895
17896       while(*p==' ') p++;
17897     }
17898
17899     /* read e.p. field in games that know e.p. capture */
17900     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17901        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17902        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17903       if(*p=='-') {
17904         p++; board[EP_STATUS] = EP_NONE;
17905       } else {
17906          char c = *p++ - AAA;
17907
17908          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17909          if(*p >= '0' && *p <='9') p++;
17910          board[EP_STATUS] = c;
17911       }
17912     }
17913
17914
17915     if(sscanf(p, "%d", &i) == 1) {
17916         FENrulePlies = i; /* 50-move ply counter */
17917         /* (The move number is still ignored)    */
17918     }
17919
17920     return TRUE;
17921 }
17922
17923 void
17924 EditPositionPasteFEN (char *fen)
17925 {
17926   if (fen != NULL) {
17927     Board initial_position;
17928
17929     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17930       DisplayError(_("Bad FEN position in clipboard"), 0);
17931       return ;
17932     } else {
17933       int savedBlackPlaysFirst = blackPlaysFirst;
17934       EditPositionEvent();
17935       blackPlaysFirst = savedBlackPlaysFirst;
17936       CopyBoard(boards[0], initial_position);
17937       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17938       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17939       DisplayBothClocks();
17940       DrawPosition(FALSE, boards[currentMove]);
17941     }
17942   }
17943 }
17944
17945 static char cseq[12] = "\\   ";
17946
17947 Boolean
17948 set_cont_sequence (char *new_seq)
17949 {
17950     int len;
17951     Boolean ret;
17952
17953     // handle bad attempts to set the sequence
17954         if (!new_seq)
17955                 return 0; // acceptable error - no debug
17956
17957     len = strlen(new_seq);
17958     ret = (len > 0) && (len < sizeof(cseq));
17959     if (ret)
17960       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17961     else if (appData.debugMode)
17962       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17963     return ret;
17964 }
17965
17966 /*
17967     reformat a source message so words don't cross the width boundary.  internal
17968     newlines are not removed.  returns the wrapped size (no null character unless
17969     included in source message).  If dest is NULL, only calculate the size required
17970     for the dest buffer.  lp argument indicats line position upon entry, and it's
17971     passed back upon exit.
17972 */
17973 int
17974 wrap (char *dest, char *src, int count, int width, int *lp)
17975 {
17976     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17977
17978     cseq_len = strlen(cseq);
17979     old_line = line = *lp;
17980     ansi = len = clen = 0;
17981
17982     for (i=0; i < count; i++)
17983     {
17984         if (src[i] == '\033')
17985             ansi = 1;
17986
17987         // if we hit the width, back up
17988         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17989         {
17990             // store i & len in case the word is too long
17991             old_i = i, old_len = len;
17992
17993             // find the end of the last word
17994             while (i && src[i] != ' ' && src[i] != '\n')
17995             {
17996                 i--;
17997                 len--;
17998             }
17999
18000             // word too long?  restore i & len before splitting it
18001             if ((old_i-i+clen) >= width)
18002             {
18003                 i = old_i;
18004                 len = old_len;
18005             }
18006
18007             // extra space?
18008             if (i && src[i-1] == ' ')
18009                 len--;
18010
18011             if (src[i] != ' ' && src[i] != '\n')
18012             {
18013                 i--;
18014                 if (len)
18015                     len--;
18016             }
18017
18018             // now append the newline and continuation sequence
18019             if (dest)
18020                 dest[len] = '\n';
18021             len++;
18022             if (dest)
18023                 strncpy(dest+len, cseq, cseq_len);
18024             len += cseq_len;
18025             line = cseq_len;
18026             clen = cseq_len;
18027             continue;
18028         }
18029
18030         if (dest)
18031             dest[len] = src[i];
18032         len++;
18033         if (!ansi)
18034             line++;
18035         if (src[i] == '\n')
18036             line = 0;
18037         if (src[i] == 'm')
18038             ansi = 0;
18039     }
18040     if (dest && appData.debugMode)
18041     {
18042         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18043             count, width, line, len, *lp);
18044         show_bytes(debugFP, src, count);
18045         fprintf(debugFP, "\ndest: ");
18046         show_bytes(debugFP, dest, len);
18047         fprintf(debugFP, "\n");
18048     }
18049     *lp = dest ? line : old_line;
18050
18051     return len;
18052 }
18053
18054 // [HGM] vari: routines for shelving variations
18055 Boolean modeRestore = FALSE;
18056
18057 void
18058 PushInner (int firstMove, int lastMove)
18059 {
18060         int i, j, nrMoves = lastMove - firstMove;
18061
18062         // push current tail of game on stack
18063         savedResult[storedGames] = gameInfo.result;
18064         savedDetails[storedGames] = gameInfo.resultDetails;
18065         gameInfo.resultDetails = NULL;
18066         savedFirst[storedGames] = firstMove;
18067         savedLast [storedGames] = lastMove;
18068         savedFramePtr[storedGames] = framePtr;
18069         framePtr -= nrMoves; // reserve space for the boards
18070         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18071             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18072             for(j=0; j<MOVE_LEN; j++)
18073                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18074             for(j=0; j<2*MOVE_LEN; j++)
18075                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18076             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18077             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18078             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18079             pvInfoList[firstMove+i-1].depth = 0;
18080             commentList[framePtr+i] = commentList[firstMove+i];
18081             commentList[firstMove+i] = NULL;
18082         }
18083
18084         storedGames++;
18085         forwardMostMove = firstMove; // truncate game so we can start variation
18086 }
18087
18088 void
18089 PushTail (int firstMove, int lastMove)
18090 {
18091         if(appData.icsActive) { // only in local mode
18092                 forwardMostMove = currentMove; // mimic old ICS behavior
18093                 return;
18094         }
18095         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18096
18097         PushInner(firstMove, lastMove);
18098         if(storedGames == 1) GreyRevert(FALSE);
18099         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18100 }
18101
18102 void
18103 PopInner (Boolean annotate)
18104 {
18105         int i, j, nrMoves;
18106         char buf[8000], moveBuf[20];
18107
18108         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18109         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18110         nrMoves = savedLast[storedGames] - currentMove;
18111         if(annotate) {
18112                 int cnt = 10;
18113                 if(!WhiteOnMove(currentMove))
18114                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18115                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18116                 for(i=currentMove; i<forwardMostMove; i++) {
18117                         if(WhiteOnMove(i))
18118                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18119                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18120                         strcat(buf, moveBuf);
18121                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18122                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18123                 }
18124                 strcat(buf, ")");
18125         }
18126         for(i=1; i<=nrMoves; i++) { // copy last variation back
18127             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18128             for(j=0; j<MOVE_LEN; j++)
18129                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18130             for(j=0; j<2*MOVE_LEN; j++)
18131                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18132             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18133             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18134             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18135             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18136             commentList[currentMove+i] = commentList[framePtr+i];
18137             commentList[framePtr+i] = NULL;
18138         }
18139         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18140         framePtr = savedFramePtr[storedGames];
18141         gameInfo.result = savedResult[storedGames];
18142         if(gameInfo.resultDetails != NULL) {
18143             free(gameInfo.resultDetails);
18144       }
18145         gameInfo.resultDetails = savedDetails[storedGames];
18146         forwardMostMove = currentMove + nrMoves;
18147 }
18148
18149 Boolean
18150 PopTail (Boolean annotate)
18151 {
18152         if(appData.icsActive) return FALSE; // only in local mode
18153         if(!storedGames) return FALSE; // sanity
18154         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18155
18156         PopInner(annotate);
18157         if(currentMove < forwardMostMove) ForwardEvent(); else
18158         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18159
18160         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18161         return TRUE;
18162 }
18163
18164 void
18165 CleanupTail ()
18166 {       // remove all shelved variations
18167         int i;
18168         for(i=0; i<storedGames; i++) {
18169             if(savedDetails[i])
18170                 free(savedDetails[i]);
18171             savedDetails[i] = NULL;
18172         }
18173         for(i=framePtr; i<MAX_MOVES; i++) {
18174                 if(commentList[i]) free(commentList[i]);
18175                 commentList[i] = NULL;
18176         }
18177         framePtr = MAX_MOVES-1;
18178         storedGames = 0;
18179 }
18180
18181 void
18182 LoadVariation (int index, char *text)
18183 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18184         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18185         int level = 0, move;
18186
18187         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18188         // first find outermost bracketing variation
18189         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18190             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18191                 if(*p == '{') wait = '}'; else
18192                 if(*p == '[') wait = ']'; else
18193                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18194                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18195             }
18196             if(*p == wait) wait = NULLCHAR; // closing ]} found
18197             p++;
18198         }
18199         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18200         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18201         end[1] = NULLCHAR; // clip off comment beyond variation
18202         ToNrEvent(currentMove-1);
18203         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18204         // kludge: use ParsePV() to append variation to game
18205         move = currentMove;
18206         ParsePV(start, TRUE, TRUE);
18207         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18208         ClearPremoveHighlights();
18209         CommentPopDown();
18210         ToNrEvent(currentMove+1);
18211 }
18212
18213 void
18214 LoadTheme ()
18215 {
18216     char *p, *q, buf[MSG_SIZ];
18217     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18218         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18219         ParseArgsFromString(buf);
18220         ActivateTheme(TRUE); // also redo colors
18221         return;
18222     }
18223     p = nickName;
18224     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18225     {
18226         int len;
18227         q = appData.themeNames;
18228         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18229       if(appData.useBitmaps) {
18230         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18231                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18232                 appData.liteBackTextureMode,
18233                 appData.darkBackTextureMode );
18234       } else {
18235         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18236                 Col2Text(2),   // lightSquareColor
18237                 Col2Text(3) ); // darkSquareColor
18238       }
18239       if(appData.useBorder) {
18240         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18241                 appData.border);
18242       } else {
18243         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18244       }
18245       if(appData.useFont) {
18246         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18247                 appData.renderPiecesWithFont,
18248                 appData.fontToPieceTable,
18249                 Col2Text(9),    // appData.fontBackColorWhite
18250                 Col2Text(10) ); // appData.fontForeColorBlack
18251       } else {
18252         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18253                 appData.pieceDirectory);
18254         if(!appData.pieceDirectory[0])
18255           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18256                 Col2Text(0),   // whitePieceColor
18257                 Col2Text(1) ); // blackPieceColor
18258       }
18259       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18260                 Col2Text(4),   // highlightSquareColor
18261                 Col2Text(5) ); // premoveHighlightColor
18262         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18263         if(insert != q) insert[-1] = NULLCHAR;
18264         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18265         if(q)   free(q);
18266     }
18267     ActivateTheme(FALSE);
18268 }