Fix Chu-Shogi Lance deferral
[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(gameInfo.variant != VariantShogi)
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         *promoChoice = PieceToChar(BlackFerz);  // no choice
6557         return FALSE;
6558     }
6559     // no sense asking what we must promote to if it is going to explode...
6560     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6561         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6562         return FALSE;
6563     }
6564     // give caller the default choice even if we will not make it
6565     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6566     partner = piece; // pieces can promote if the pieceToCharTable says so
6567     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6568     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6569     if(        sweepSelect && gameInfo.variant != VariantGreat
6570                            && gameInfo.variant != VariantGrand
6571                            && gameInfo.variant != VariantSuper) return FALSE;
6572     if(autoQueen) return FALSE; // predetermined
6573
6574     // suppress promotion popup on illegal moves that are not premoves
6575     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6576               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6577     if(appData.testLegality && !premove) {
6578         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6579                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6580         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6581         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6582             return FALSE;
6583     }
6584
6585     return TRUE;
6586 }
6587
6588 int
6589 InPalace (int row, int column)
6590 {   /* [HGM] for Xiangqi */
6591     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6592          column < (BOARD_WIDTH + 4)/2 &&
6593          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6594     return FALSE;
6595 }
6596
6597 int
6598 PieceForSquare (int x, int y)
6599 {
6600   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6601      return -1;
6602   else
6603      return boards[currentMove][y][x];
6604 }
6605
6606 int
6607 OKToStartUserMove (int x, int y)
6608 {
6609     ChessSquare from_piece;
6610     int white_piece;
6611
6612     if (matchMode) return FALSE;
6613     if (gameMode == EditPosition) return TRUE;
6614
6615     if (x >= 0 && y >= 0)
6616       from_piece = boards[currentMove][y][x];
6617     else
6618       from_piece = EmptySquare;
6619
6620     if (from_piece == EmptySquare) return FALSE;
6621
6622     white_piece = (int)from_piece >= (int)WhitePawn &&
6623       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6624
6625     switch (gameMode) {
6626       case AnalyzeFile:
6627       case TwoMachinesPlay:
6628       case EndOfGame:
6629         return FALSE;
6630
6631       case IcsObserving:
6632       case IcsIdle:
6633         return FALSE;
6634
6635       case MachinePlaysWhite:
6636       case IcsPlayingBlack:
6637         if (appData.zippyPlay) return FALSE;
6638         if (white_piece) {
6639             DisplayMoveError(_("You are playing Black"));
6640             return FALSE;
6641         }
6642         break;
6643
6644       case MachinePlaysBlack:
6645       case IcsPlayingWhite:
6646         if (appData.zippyPlay) return FALSE;
6647         if (!white_piece) {
6648             DisplayMoveError(_("You are playing White"));
6649             return FALSE;
6650         }
6651         break;
6652
6653       case PlayFromGameFile:
6654             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6655       case EditGame:
6656         if (!white_piece && WhiteOnMove(currentMove)) {
6657             DisplayMoveError(_("It is White's turn"));
6658             return FALSE;
6659         }
6660         if (white_piece && !WhiteOnMove(currentMove)) {
6661             DisplayMoveError(_("It is Black's turn"));
6662             return FALSE;
6663         }
6664         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6665             /* Editing correspondence game history */
6666             /* Could disallow this or prompt for confirmation */
6667             cmailOldMove = -1;
6668         }
6669         break;
6670
6671       case BeginningOfGame:
6672         if (appData.icsActive) return FALSE;
6673         if (!appData.noChessProgram) {
6674             if (!white_piece) {
6675                 DisplayMoveError(_("You are playing White"));
6676                 return FALSE;
6677             }
6678         }
6679         break;
6680
6681       case Training:
6682         if (!white_piece && WhiteOnMove(currentMove)) {
6683             DisplayMoveError(_("It is White's turn"));
6684             return FALSE;
6685         }
6686         if (white_piece && !WhiteOnMove(currentMove)) {
6687             DisplayMoveError(_("It is Black's turn"));
6688             return FALSE;
6689         }
6690         break;
6691
6692       default:
6693       case IcsExamining:
6694         break;
6695     }
6696     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6697         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6698         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6699         && gameMode != AnalyzeFile && gameMode != Training) {
6700         DisplayMoveError(_("Displayed position is not current"));
6701         return FALSE;
6702     }
6703     return TRUE;
6704 }
6705
6706 Boolean
6707 OnlyMove (int *x, int *y, Boolean captures)
6708 {
6709     DisambiguateClosure cl;
6710     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6711     switch(gameMode) {
6712       case MachinePlaysBlack:
6713       case IcsPlayingWhite:
6714       case BeginningOfGame:
6715         if(!WhiteOnMove(currentMove)) return FALSE;
6716         break;
6717       case MachinePlaysWhite:
6718       case IcsPlayingBlack:
6719         if(WhiteOnMove(currentMove)) return FALSE;
6720         break;
6721       case EditGame:
6722         break;
6723       default:
6724         return FALSE;
6725     }
6726     cl.pieceIn = EmptySquare;
6727     cl.rfIn = *y;
6728     cl.ffIn = *x;
6729     cl.rtIn = -1;
6730     cl.ftIn = -1;
6731     cl.promoCharIn = NULLCHAR;
6732     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6733     if( cl.kind == NormalMove ||
6734         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6735         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6736         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6737       fromX = cl.ff;
6738       fromY = cl.rf;
6739       *x = cl.ft;
6740       *y = cl.rt;
6741       return TRUE;
6742     }
6743     if(cl.kind != ImpossibleMove) return FALSE;
6744     cl.pieceIn = EmptySquare;
6745     cl.rfIn = -1;
6746     cl.ffIn = -1;
6747     cl.rtIn = *y;
6748     cl.ftIn = *x;
6749     cl.promoCharIn = NULLCHAR;
6750     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6751     if( cl.kind == NormalMove ||
6752         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6753         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6754         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6755       fromX = cl.ff;
6756       fromY = cl.rf;
6757       *x = cl.ft;
6758       *y = cl.rt;
6759       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6760       return TRUE;
6761     }
6762     return FALSE;
6763 }
6764
6765 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6766 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6767 int lastLoadGameUseList = FALSE;
6768 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6769 ChessMove lastLoadGameStart = EndOfFile;
6770 int doubleClick;
6771
6772 void
6773 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6774 {
6775     ChessMove moveType;
6776     ChessSquare pup;
6777     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6778
6779     /* Check if the user is playing in turn.  This is complicated because we
6780        let the user "pick up" a piece before it is his turn.  So the piece he
6781        tried to pick up may have been captured by the time he puts it down!
6782        Therefore we use the color the user is supposed to be playing in this
6783        test, not the color of the piece that is currently on the starting
6784        square---except in EditGame mode, where the user is playing both
6785        sides; fortunately there the capture race can't happen.  (It can
6786        now happen in IcsExamining mode, but that's just too bad.  The user
6787        will get a somewhat confusing message in that case.)
6788        */
6789
6790     switch (gameMode) {
6791       case AnalyzeFile:
6792       case TwoMachinesPlay:
6793       case EndOfGame:
6794       case IcsObserving:
6795       case IcsIdle:
6796         /* We switched into a game mode where moves are not accepted,
6797            perhaps while the mouse button was down. */
6798         return;
6799
6800       case MachinePlaysWhite:
6801         /* User is moving for Black */
6802         if (WhiteOnMove(currentMove)) {
6803             DisplayMoveError(_("It is White's turn"));
6804             return;
6805         }
6806         break;
6807
6808       case MachinePlaysBlack:
6809         /* User is moving for White */
6810         if (!WhiteOnMove(currentMove)) {
6811             DisplayMoveError(_("It is Black's turn"));
6812             return;
6813         }
6814         break;
6815
6816       case PlayFromGameFile:
6817             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6818       case EditGame:
6819       case IcsExamining:
6820       case BeginningOfGame:
6821       case AnalyzeMode:
6822       case Training:
6823         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6824         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6825             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6826             /* User is moving for Black */
6827             if (WhiteOnMove(currentMove)) {
6828                 DisplayMoveError(_("It is White's turn"));
6829                 return;
6830             }
6831         } else {
6832             /* User is moving for White */
6833             if (!WhiteOnMove(currentMove)) {
6834                 DisplayMoveError(_("It is Black's turn"));
6835                 return;
6836             }
6837         }
6838         break;
6839
6840       case IcsPlayingBlack:
6841         /* User is moving for Black */
6842         if (WhiteOnMove(currentMove)) {
6843             if (!appData.premove) {
6844                 DisplayMoveError(_("It is White's turn"));
6845             } else if (toX >= 0 && toY >= 0) {
6846                 premoveToX = toX;
6847                 premoveToY = toY;
6848                 premoveFromX = fromX;
6849                 premoveFromY = fromY;
6850                 premovePromoChar = promoChar;
6851                 gotPremove = 1;
6852                 if (appData.debugMode)
6853                     fprintf(debugFP, "Got premove: fromX %d,"
6854                             "fromY %d, toX %d, toY %d\n",
6855                             fromX, fromY, toX, toY);
6856             }
6857             return;
6858         }
6859         break;
6860
6861       case IcsPlayingWhite:
6862         /* User is moving for White */
6863         if (!WhiteOnMove(currentMove)) {
6864             if (!appData.premove) {
6865                 DisplayMoveError(_("It is Black's turn"));
6866             } else if (toX >= 0 && toY >= 0) {
6867                 premoveToX = toX;
6868                 premoveToY = toY;
6869                 premoveFromX = fromX;
6870                 premoveFromY = fromY;
6871                 premovePromoChar = promoChar;
6872                 gotPremove = 1;
6873                 if (appData.debugMode)
6874                     fprintf(debugFP, "Got premove: fromX %d,"
6875                             "fromY %d, toX %d, toY %d\n",
6876                             fromX, fromY, toX, toY);
6877             }
6878             return;
6879         }
6880         break;
6881
6882       default:
6883         break;
6884
6885       case EditPosition:
6886         /* EditPosition, empty square, or different color piece;
6887            click-click move is possible */
6888         if (toX == -2 || toY == -2) {
6889             boards[0][fromY][fromX] = EmptySquare;
6890             DrawPosition(FALSE, boards[currentMove]);
6891             return;
6892         } else if (toX >= 0 && toY >= 0) {
6893             boards[0][toY][toX] = boards[0][fromY][fromX];
6894             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6895                 if(boards[0][fromY][0] != EmptySquare) {
6896                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6897                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6898                 }
6899             } else
6900             if(fromX == BOARD_RGHT+1) {
6901                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6902                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6903                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6904                 }
6905             } else
6906             boards[0][fromY][fromX] = gatingPiece;
6907             DrawPosition(FALSE, boards[currentMove]);
6908             return;
6909         }
6910         return;
6911     }
6912
6913     if(toX < 0 || toY < 0) return;
6914     pup = boards[currentMove][toY][toX];
6915
6916     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6917     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6918          if( pup != EmptySquare ) return;
6919          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6920            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6921                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6922            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6923            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6924            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6925            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6926          fromY = DROP_RANK;
6927     }
6928
6929     /* [HGM] always test for legality, to get promotion info */
6930     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6931                                          fromY, fromX, toY, toX, promoChar);
6932
6933     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6934
6935     /* [HGM] but possibly ignore an IllegalMove result */
6936     if (appData.testLegality) {
6937         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6938             DisplayMoveError(_("Illegal move"));
6939             return;
6940         }
6941     }
6942
6943     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6944         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6945              ClearPremoveHighlights(); // was included
6946         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6947         return;
6948     }
6949
6950     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6951 }
6952
6953 /* Common tail of UserMoveEvent and DropMenuEvent */
6954 int
6955 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6956 {
6957     char *bookHit = 0;
6958
6959     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6960         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6961         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6962         if(WhiteOnMove(currentMove)) {
6963             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6964         } else {
6965             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6966         }
6967     }
6968
6969     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6970        move type in caller when we know the move is a legal promotion */
6971     if(moveType == NormalMove && promoChar)
6972         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6973
6974     /* [HGM] <popupFix> The following if has been moved here from
6975        UserMoveEvent(). Because it seemed to belong here (why not allow
6976        piece drops in training games?), and because it can only be
6977        performed after it is known to what we promote. */
6978     if (gameMode == Training) {
6979       /* compare the move played on the board to the next move in the
6980        * game. If they match, display the move and the opponent's response.
6981        * If they don't match, display an error message.
6982        */
6983       int saveAnimate;
6984       Board testBoard;
6985       CopyBoard(testBoard, boards[currentMove]);
6986       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6987
6988       if (CompareBoards(testBoard, boards[currentMove+1])) {
6989         ForwardInner(currentMove+1);
6990
6991         /* Autoplay the opponent's response.
6992          * if appData.animate was TRUE when Training mode was entered,
6993          * the response will be animated.
6994          */
6995         saveAnimate = appData.animate;
6996         appData.animate = animateTraining;
6997         ForwardInner(currentMove+1);
6998         appData.animate = saveAnimate;
6999
7000         /* check for the end of the game */
7001         if (currentMove >= forwardMostMove) {
7002           gameMode = PlayFromGameFile;
7003           ModeHighlight();
7004           SetTrainingModeOff();
7005           DisplayInformation(_("End of game"));
7006         }
7007       } else {
7008         DisplayError(_("Incorrect move"), 0);
7009       }
7010       return 1;
7011     }
7012
7013   /* Ok, now we know that the move is good, so we can kill
7014      the previous line in Analysis Mode */
7015   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7016                                 && currentMove < forwardMostMove) {
7017     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7018     else forwardMostMove = currentMove;
7019   }
7020
7021   ClearMap();
7022
7023   /* If we need the chess program but it's dead, restart it */
7024   ResurrectChessProgram();
7025
7026   /* A user move restarts a paused game*/
7027   if (pausing)
7028     PauseEvent();
7029
7030   thinkOutput[0] = NULLCHAR;
7031
7032   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7033
7034   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7035     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7036     return 1;
7037   }
7038
7039   if (gameMode == BeginningOfGame) {
7040     if (appData.noChessProgram) {
7041       gameMode = EditGame;
7042       SetGameInfo();
7043     } else {
7044       char buf[MSG_SIZ];
7045       gameMode = MachinePlaysBlack;
7046       StartClocks();
7047       SetGameInfo();
7048       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7049       DisplayTitle(buf);
7050       if (first.sendName) {
7051         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7052         SendToProgram(buf, &first);
7053       }
7054       StartClocks();
7055     }
7056     ModeHighlight();
7057   }
7058
7059   /* Relay move to ICS or chess engine */
7060   if (appData.icsActive) {
7061     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7062         gameMode == IcsExamining) {
7063       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7064         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7065         SendToICS("draw ");
7066         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7067       }
7068       // also send plain move, in case ICS does not understand atomic claims
7069       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7070       ics_user_moved = 1;
7071     }
7072   } else {
7073     if (first.sendTime && (gameMode == BeginningOfGame ||
7074                            gameMode == MachinePlaysWhite ||
7075                            gameMode == MachinePlaysBlack)) {
7076       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7077     }
7078     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7079          // [HGM] book: if program might be playing, let it use book
7080         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7081         first.maybeThinking = TRUE;
7082     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7083         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7084         SendBoard(&first, currentMove+1);
7085         if(second.analyzing) {
7086             if(!second.useSetboard) SendToProgram("undo\n", &second);
7087             SendBoard(&second, currentMove+1);
7088         }
7089     } else {
7090         SendMoveToProgram(forwardMostMove-1, &first);
7091         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7092     }
7093     if (currentMove == cmailOldMove + 1) {
7094       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7095     }
7096   }
7097
7098   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7099
7100   switch (gameMode) {
7101   case EditGame:
7102     if(appData.testLegality)
7103     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7104     case MT_NONE:
7105     case MT_CHECK:
7106       break;
7107     case MT_CHECKMATE:
7108     case MT_STAINMATE:
7109       if (WhiteOnMove(currentMove)) {
7110         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7111       } else {
7112         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7113       }
7114       break;
7115     case MT_STALEMATE:
7116       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7117       break;
7118     }
7119     break;
7120
7121   case MachinePlaysBlack:
7122   case MachinePlaysWhite:
7123     /* disable certain menu options while machine is thinking */
7124     SetMachineThinkingEnables();
7125     break;
7126
7127   default:
7128     break;
7129   }
7130
7131   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7132   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7133
7134   if(bookHit) { // [HGM] book: simulate book reply
7135         static char bookMove[MSG_SIZ]; // a bit generous?
7136
7137         programStats.nodes = programStats.depth = programStats.time =
7138         programStats.score = programStats.got_only_move = 0;
7139         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7140
7141         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7142         strcat(bookMove, bookHit);
7143         HandleMachineMove(bookMove, &first);
7144   }
7145   return 1;
7146 }
7147
7148 void
7149 MarkByFEN(char *fen)
7150 {
7151         int r, f;
7152         if(!appData.markers || !appData.highlightDragging) return;
7153         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7154         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7155         while(*fen) {
7156             int s = 0;
7157             marker[r][f] = 0;
7158             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7159             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7160             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7161             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7162             if(*fen == 'T') marker[r][f++] = 0; else
7163             if(*fen == 'Y') marker[r][f++] = 1; else
7164             if(*fen == 'G') marker[r][f++] = 3; else
7165             if(*fen == 'B') marker[r][f++] = 4; else
7166             if(*fen == 'C') marker[r][f++] = 5; else
7167             if(*fen == 'M') marker[r][f++] = 6; else
7168             if(*fen == 'W') marker[r][f++] = 7; else
7169             if(*fen == 'D') marker[r][f++] = 8; else
7170             if(*fen == 'R') marker[r][f++] = 2; else {
7171                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7172               f += s; fen -= s>0;
7173             }
7174             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7175             if(r < 0) break;
7176             fen++;
7177         }
7178         DrawPosition(TRUE, NULL);
7179 }
7180
7181 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7182
7183 void
7184 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7185 {
7186     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7187     Markers *m = (Markers *) closure;
7188     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7189         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7190                          || kind == WhiteCapturesEnPassant
7191                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7192     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7193 }
7194
7195 static int hoverSavedValid;
7196
7197 void
7198 MarkTargetSquares (int clear)
7199 {
7200   int x, y, sum=0;
7201   if(clear) { // no reason to ever suppress clearing
7202     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7203     hoverSavedValid = 0;
7204     if(!sum) return; // nothing was cleared,no redraw needed
7205   } else {
7206     int capt = 0;
7207     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7208        !appData.testLegality || gameMode == EditPosition) return;
7209     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7210     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7211       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7212       if(capt)
7213       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7214     }
7215   }
7216   DrawPosition(FALSE, NULL);
7217 }
7218
7219 int
7220 Explode (Board board, int fromX, int fromY, int toX, int toY)
7221 {
7222     if(gameInfo.variant == VariantAtomic &&
7223        (board[toY][toX] != EmptySquare ||                     // capture?
7224         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7225                          board[fromY][fromX] == BlackPawn   )
7226       )) {
7227         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7228         return TRUE;
7229     }
7230     return FALSE;
7231 }
7232
7233 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7234
7235 int
7236 CanPromote (ChessSquare piece, int y)
7237 {
7238         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7239         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7240         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7241         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7242            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7243            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7244          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7245         return (piece == BlackPawn && y <= zone ||
7246                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7247                 piece == BlackLance && y == 1 ||
7248                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7249 }
7250
7251 void
7252 HoverEvent (int xPix, int yPix, int x, int y)
7253 {
7254         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7255         int r, f;
7256         if(!first.highlight) return;
7257         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7258         if(x == oldX && y == oldY) return; // only do something if we enter new square
7259         oldFromX = fromX; oldFromY = fromY;
7260         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7261           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7262             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7263           hoverSavedValid = 1;
7264         } else if(oldX != x || oldY != y) {
7265           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7266           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7267           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7268             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7269           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7270             char buf[MSG_SIZ];
7271             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7272             SendToProgram(buf, &first);
7273           }
7274           oldX = x; oldY = y;
7275 //        SetHighlights(fromX, fromY, x, y);
7276         }
7277 }
7278
7279 void ReportClick(char *action, int x, int y)
7280 {
7281         char buf[MSG_SIZ]; // Inform engine of what user does
7282         int r, f;
7283         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7284           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7285         if(!first.highlight || gameMode == EditPosition) return;
7286         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7287         SendToProgram(buf, &first);
7288 }
7289
7290 void
7291 LeftClick (ClickType clickType, int xPix, int yPix)
7292 {
7293     int x, y;
7294     Boolean saveAnimate;
7295     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7296     char promoChoice = NULLCHAR;
7297     ChessSquare piece;
7298     static TimeMark lastClickTime, prevClickTime;
7299
7300     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7301
7302     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7303
7304     if (clickType == Press) ErrorPopDown();
7305     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7306
7307     x = EventToSquare(xPix, BOARD_WIDTH);
7308     y = EventToSquare(yPix, BOARD_HEIGHT);
7309     if (!flipView && y >= 0) {
7310         y = BOARD_HEIGHT - 1 - y;
7311     }
7312     if (flipView && x >= 0) {
7313         x = BOARD_WIDTH - 1 - x;
7314     }
7315
7316     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7317         defaultPromoChoice = promoSweep;
7318         promoSweep = EmptySquare;   // terminate sweep
7319         promoDefaultAltered = TRUE;
7320         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7321     }
7322
7323     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7324         if(clickType == Release) return; // ignore upclick of click-click destination
7325         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7326         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7327         if(gameInfo.holdingsWidth &&
7328                 (WhiteOnMove(currentMove)
7329                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7330                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7331             // click in right holdings, for determining promotion piece
7332             ChessSquare p = boards[currentMove][y][x];
7333             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7334             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7335             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7336                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7337                 fromX = fromY = -1;
7338                 return;
7339             }
7340         }
7341         DrawPosition(FALSE, boards[currentMove]);
7342         return;
7343     }
7344
7345     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7346     if(clickType == Press
7347             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7348               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7349               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7350         return;
7351
7352     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7353         // could be static click on premove from-square: abort premove
7354         gotPremove = 0;
7355         ClearPremoveHighlights();
7356     }
7357
7358     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7359         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7360
7361     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7362         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7363                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7364         defaultPromoChoice = DefaultPromoChoice(side);
7365     }
7366
7367     autoQueen = appData.alwaysPromoteToQueen;
7368
7369     if (fromX == -1) {
7370       int originalY = y;
7371       gatingPiece = EmptySquare;
7372       if (clickType != Press) {
7373         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7374             DragPieceEnd(xPix, yPix); dragging = 0;
7375             DrawPosition(FALSE, NULL);
7376         }
7377         return;
7378       }
7379       doubleClick = FALSE;
7380       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7381         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7382       }
7383       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7384       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7385          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7386          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7387             /* First square */
7388             if (OKToStartUserMove(fromX, fromY)) {
7389                 second = 0;
7390                 ReportClick("lift", x, y);
7391                 MarkTargetSquares(0);
7392                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7393                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7394                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7395                     promoSweep = defaultPromoChoice;
7396                     selectFlag = 0; lastX = xPix; lastY = yPix;
7397                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7398                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7399                 }
7400                 if (appData.highlightDragging) {
7401                     SetHighlights(fromX, fromY, -1, -1);
7402                 } else {
7403                     ClearHighlights();
7404                 }
7405             } else fromX = fromY = -1;
7406             return;
7407         }
7408     }
7409
7410     /* fromX != -1 */
7411     if (clickType == Press && gameMode != EditPosition) {
7412         ChessSquare fromP;
7413         ChessSquare toP;
7414         int frc;
7415
7416         // ignore off-board to clicks
7417         if(y < 0 || x < 0) return;
7418
7419         /* Check if clicking again on the same color piece */
7420         fromP = boards[currentMove][fromY][fromX];
7421         toP = boards[currentMove][y][x];
7422         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7423         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7424            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7425              WhitePawn <= toP && toP <= WhiteKing &&
7426              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7427              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7428             (BlackPawn <= fromP && fromP <= BlackKing &&
7429              BlackPawn <= toP && toP <= BlackKing &&
7430              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7431              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7432             /* Clicked again on same color piece -- changed his mind */
7433             second = (x == fromX && y == fromY);
7434             killX = killY = -1;
7435             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7436                 second = FALSE; // first double-click rather than scond click
7437                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7438             }
7439             promoDefaultAltered = FALSE;
7440             MarkTargetSquares(1);
7441            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7442             if (appData.highlightDragging) {
7443                 SetHighlights(x, y, -1, -1);
7444             } else {
7445                 ClearHighlights();
7446             }
7447             if (OKToStartUserMove(x, y)) {
7448                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7449                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7450                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7451                  gatingPiece = boards[currentMove][fromY][fromX];
7452                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7453                 fromX = x;
7454                 fromY = y; dragging = 1;
7455                 ReportClick("lift", x, y);
7456                 MarkTargetSquares(0);
7457                 DragPieceBegin(xPix, yPix, FALSE);
7458                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7459                     promoSweep = defaultPromoChoice;
7460                     selectFlag = 0; lastX = xPix; lastY = yPix;
7461                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7462                 }
7463             }
7464            }
7465            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7466            second = FALSE;
7467         }
7468         // ignore clicks on holdings
7469         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7470     }
7471
7472     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7473         DragPieceEnd(xPix, yPix); dragging = 0;
7474         if(clearFlag) {
7475             // a deferred attempt to click-click move an empty square on top of a piece
7476             boards[currentMove][y][x] = EmptySquare;
7477             ClearHighlights();
7478             DrawPosition(FALSE, boards[currentMove]);
7479             fromX = fromY = -1; clearFlag = 0;
7480             return;
7481         }
7482         if (appData.animateDragging) {
7483             /* Undo animation damage if any */
7484             DrawPosition(FALSE, NULL);
7485         }
7486         if (second || sweepSelecting) {
7487             /* Second up/down in same square; just abort move */
7488             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7489             second = sweepSelecting = 0;
7490             fromX = fromY = -1;
7491             gatingPiece = EmptySquare;
7492             MarkTargetSquares(1);
7493             ClearHighlights();
7494             gotPremove = 0;
7495             ClearPremoveHighlights();
7496         } else {
7497             /* First upclick in same square; start click-click mode */
7498             SetHighlights(x, y, -1, -1);
7499         }
7500         return;
7501     }
7502
7503     clearFlag = 0;
7504
7505     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7506         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7507         DisplayMessage(_("only marked squares are legal"),"");
7508         DrawPosition(TRUE, NULL);
7509         return; // ignore to-click
7510     }
7511
7512     /* we now have a different from- and (possibly off-board) to-square */
7513     /* Completed move */
7514     if(!sweepSelecting) {
7515         toX = x;
7516         toY = y;
7517     }
7518
7519     piece = boards[currentMove][fromY][fromX];
7520
7521     saveAnimate = appData.animate;
7522     if (clickType == Press) {
7523         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7524         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7525             // must be Edit Position mode with empty-square selected
7526             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7527             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7528             return;
7529         }
7530         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7531             return;
7532         }
7533         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7534             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7535         } else
7536         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7537         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7538           if(appData.sweepSelect) {
7539             promoSweep = defaultPromoChoice;
7540             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7541             selectFlag = 0; lastX = xPix; lastY = yPix;
7542             Sweep(0); // Pawn that is going to promote: preview promotion piece
7543             sweepSelecting = 1;
7544             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7545             MarkTargetSquares(1);
7546           }
7547           return; // promo popup appears on up-click
7548         }
7549         /* Finish clickclick move */
7550         if (appData.animate || appData.highlightLastMove) {
7551             SetHighlights(fromX, fromY, toX, toY);
7552         } else {
7553             ClearHighlights();
7554         }
7555     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7556         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7557         if (appData.animate || appData.highlightLastMove) {
7558             SetHighlights(fromX, fromY, toX, toY);
7559         } else {
7560             ClearHighlights();
7561         }
7562     } else {
7563 #if 0
7564 // [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
7565         /* Finish drag move */
7566         if (appData.highlightLastMove) {
7567             SetHighlights(fromX, fromY, toX, toY);
7568         } else {
7569             ClearHighlights();
7570         }
7571 #endif
7572         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7573         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7574           dragging *= 2;            // flag button-less dragging if we are dragging
7575           MarkTargetSquares(1);
7576           if(x == killX && y == killY) killX = killY = -1; else {
7577             killX = x; killY = y;     //remeber this square as intermediate
7578             ReportClick("put", x, y); // and inform engine
7579             ReportClick("lift", x, y);
7580             MarkTargetSquares(0);
7581             return;
7582           }
7583         }
7584         DragPieceEnd(xPix, yPix); dragging = 0;
7585         /* Don't animate move and drag both */
7586         appData.animate = FALSE;
7587     }
7588
7589     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7590     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7591         ChessSquare piece = boards[currentMove][fromY][fromX];
7592         if(gameMode == EditPosition && piece != EmptySquare &&
7593            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7594             int n;
7595
7596             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7597                 n = PieceToNumber(piece - (int)BlackPawn);
7598                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7599                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7600                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7601             } else
7602             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7603                 n = PieceToNumber(piece);
7604                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7605                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7606                 boards[currentMove][n][BOARD_WIDTH-2]++;
7607             }
7608             boards[currentMove][fromY][fromX] = EmptySquare;
7609         }
7610         ClearHighlights();
7611         fromX = fromY = -1;
7612         MarkTargetSquares(1);
7613         DrawPosition(TRUE, boards[currentMove]);
7614         return;
7615     }
7616
7617     // off-board moves should not be highlighted
7618     if(x < 0 || y < 0) ClearHighlights();
7619     else ReportClick("put", x, y);
7620
7621     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7622
7623     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7624         SetHighlights(fromX, fromY, toX, toY);
7625         MarkTargetSquares(1);
7626         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7627             // [HGM] super: promotion to captured piece selected from holdings
7628             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7629             promotionChoice = TRUE;
7630             // kludge follows to temporarily execute move on display, without promoting yet
7631             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7632             boards[currentMove][toY][toX] = p;
7633             DrawPosition(FALSE, boards[currentMove]);
7634             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7635             boards[currentMove][toY][toX] = q;
7636             DisplayMessage("Click in holdings to choose piece", "");
7637             return;
7638         }
7639         PromotionPopUp(promoChoice);
7640     } else {
7641         int oldMove = currentMove;
7642         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7643         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7644         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7645         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7646            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7647             DrawPosition(TRUE, boards[currentMove]);
7648         MarkTargetSquares(1);
7649         fromX = fromY = -1;
7650     }
7651     appData.animate = saveAnimate;
7652     if (appData.animate || appData.animateDragging) {
7653         /* Undo animation damage if needed */
7654         DrawPosition(FALSE, NULL);
7655     }
7656 }
7657
7658 int
7659 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7660 {   // front-end-free part taken out of PieceMenuPopup
7661     int whichMenu; int xSqr, ySqr;
7662
7663     if(seekGraphUp) { // [HGM] seekgraph
7664         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7665         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7666         return -2;
7667     }
7668
7669     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7670          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7671         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7672         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7673         if(action == Press)   {
7674             originalFlip = flipView;
7675             flipView = !flipView; // temporarily flip board to see game from partners perspective
7676             DrawPosition(TRUE, partnerBoard);
7677             DisplayMessage(partnerStatus, "");
7678             partnerUp = TRUE;
7679         } else if(action == Release) {
7680             flipView = originalFlip;
7681             DrawPosition(TRUE, boards[currentMove]);
7682             partnerUp = FALSE;
7683         }
7684         return -2;
7685     }
7686
7687     xSqr = EventToSquare(x, BOARD_WIDTH);
7688     ySqr = EventToSquare(y, BOARD_HEIGHT);
7689     if (action == Release) {
7690         if(pieceSweep != EmptySquare) {
7691             EditPositionMenuEvent(pieceSweep, toX, toY);
7692             pieceSweep = EmptySquare;
7693         } else UnLoadPV(); // [HGM] pv
7694     }
7695     if (action != Press) return -2; // return code to be ignored
7696     switch (gameMode) {
7697       case IcsExamining:
7698         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7699       case EditPosition:
7700         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7701         if (xSqr < 0 || ySqr < 0) return -1;
7702         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7703         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7704         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7705         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7706         NextPiece(0);
7707         return 2; // grab
7708       case IcsObserving:
7709         if(!appData.icsEngineAnalyze) return -1;
7710       case IcsPlayingWhite:
7711       case IcsPlayingBlack:
7712         if(!appData.zippyPlay) goto noZip;
7713       case AnalyzeMode:
7714       case AnalyzeFile:
7715       case MachinePlaysWhite:
7716       case MachinePlaysBlack:
7717       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7718         if (!appData.dropMenu) {
7719           LoadPV(x, y);
7720           return 2; // flag front-end to grab mouse events
7721         }
7722         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7723            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7724       case EditGame:
7725       noZip:
7726         if (xSqr < 0 || ySqr < 0) return -1;
7727         if (!appData.dropMenu || appData.testLegality &&
7728             gameInfo.variant != VariantBughouse &&
7729             gameInfo.variant != VariantCrazyhouse) return -1;
7730         whichMenu = 1; // drop menu
7731         break;
7732       default:
7733         return -1;
7734     }
7735
7736     if (((*fromX = xSqr) < 0) ||
7737         ((*fromY = ySqr) < 0)) {
7738         *fromX = *fromY = -1;
7739         return -1;
7740     }
7741     if (flipView)
7742       *fromX = BOARD_WIDTH - 1 - *fromX;
7743     else
7744       *fromY = BOARD_HEIGHT - 1 - *fromY;
7745
7746     return whichMenu;
7747 }
7748
7749 void
7750 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7751 {
7752 //    char * hint = lastHint;
7753     FrontEndProgramStats stats;
7754
7755     stats.which = cps == &first ? 0 : 1;
7756     stats.depth = cpstats->depth;
7757     stats.nodes = cpstats->nodes;
7758     stats.score = cpstats->score;
7759     stats.time = cpstats->time;
7760     stats.pv = cpstats->movelist;
7761     stats.hint = lastHint;
7762     stats.an_move_index = 0;
7763     stats.an_move_count = 0;
7764
7765     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7766         stats.hint = cpstats->move_name;
7767         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7768         stats.an_move_count = cpstats->nr_moves;
7769     }
7770
7771     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
7772
7773     SetProgramStats( &stats );
7774 }
7775
7776 void
7777 ClearEngineOutputPane (int which)
7778 {
7779     static FrontEndProgramStats dummyStats;
7780     dummyStats.which = which;
7781     dummyStats.pv = "#";
7782     SetProgramStats( &dummyStats );
7783 }
7784
7785 #define MAXPLAYERS 500
7786
7787 char *
7788 TourneyStandings (int display)
7789 {
7790     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7791     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7792     char result, *p, *names[MAXPLAYERS];
7793
7794     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7795         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7796     names[0] = p = strdup(appData.participants);
7797     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7798
7799     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7800
7801     while(result = appData.results[nr]) {
7802         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7803         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7804         wScore = bScore = 0;
7805         switch(result) {
7806           case '+': wScore = 2; break;
7807           case '-': bScore = 2; break;
7808           case '=': wScore = bScore = 1; break;
7809           case ' ':
7810           case '*': return strdup("busy"); // tourney not finished
7811         }
7812         score[w] += wScore;
7813         score[b] += bScore;
7814         games[w]++;
7815         games[b]++;
7816         nr++;
7817     }
7818     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7819     for(w=0; w<nPlayers; w++) {
7820         bScore = -1;
7821         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7822         ranking[w] = b; points[w] = bScore; score[b] = -2;
7823     }
7824     p = malloc(nPlayers*34+1);
7825     for(w=0; w<nPlayers && w<display; w++)
7826         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7827     free(names[0]);
7828     return p;
7829 }
7830
7831 void
7832 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7833 {       // count all piece types
7834         int p, f, r;
7835         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7836         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7837         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7838                 p = board[r][f];
7839                 pCnt[p]++;
7840                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7841                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7842                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7843                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7844                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7845                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7846         }
7847 }
7848
7849 int
7850 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7851 {
7852         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7853         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7854
7855         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7856         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7857         if(myPawns == 2 && nMine == 3) // KPP
7858             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7859         if(myPawns == 1 && nMine == 2) // KP
7860             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7861         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7862             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7863         if(myPawns) return FALSE;
7864         if(pCnt[WhiteRook+side])
7865             return pCnt[BlackRook-side] ||
7866                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7867                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7868                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7869         if(pCnt[WhiteCannon+side]) {
7870             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7871             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7872         }
7873         if(pCnt[WhiteKnight+side])
7874             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7875         return FALSE;
7876 }
7877
7878 int
7879 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7880 {
7881         VariantClass v = gameInfo.variant;
7882
7883         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7884         if(v == VariantShatranj) return TRUE; // always winnable through baring
7885         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7886         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7887
7888         if(v == VariantXiangqi) {
7889                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7890
7891                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7892                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7893                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7894                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7895                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7896                 if(stale) // we have at least one last-rank P plus perhaps C
7897                     return majors // KPKX
7898                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7899                 else // KCA*E*
7900                     return pCnt[WhiteFerz+side] // KCAK
7901                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7902                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7903                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7904
7905         } else if(v == VariantKnightmate) {
7906                 if(nMine == 1) return FALSE;
7907                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7908         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7909                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7910
7911                 if(nMine == 1) return FALSE; // bare King
7912                 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
7913                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7914                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7915                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7916                 if(pCnt[WhiteKnight+side])
7917                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7918                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7919                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7920                 if(nBishops)
7921                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7922                 if(pCnt[WhiteAlfil+side])
7923                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7924                 if(pCnt[WhiteWazir+side])
7925                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7926         }
7927
7928         return TRUE;
7929 }
7930
7931 int
7932 CompareWithRights (Board b1, Board b2)
7933 {
7934     int rights = 0;
7935     if(!CompareBoards(b1, b2)) return FALSE;
7936     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7937     /* compare castling rights */
7938     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7939            rights++; /* King lost rights, while rook still had them */
7940     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7941         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7942            rights++; /* but at least one rook lost them */
7943     }
7944     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7945            rights++;
7946     if( b1[CASTLING][5] != NoRights ) {
7947         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7948            rights++;
7949     }
7950     return rights == 0;
7951 }
7952
7953 int
7954 Adjudicate (ChessProgramState *cps)
7955 {       // [HGM] some adjudications useful with buggy engines
7956         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7957         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7958         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7959         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7960         int k, drop, count = 0; static int bare = 1;
7961         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7962         Boolean canAdjudicate = !appData.icsActive;
7963
7964         // most tests only when we understand the game, i.e. legality-checking on
7965             if( appData.testLegality )
7966             {   /* [HGM] Some more adjudications for obstinate engines */
7967                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7968                 static int moveCount = 6;
7969                 ChessMove result;
7970                 char *reason = NULL;
7971
7972                 /* Count what is on board. */
7973                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7974
7975                 /* Some material-based adjudications that have to be made before stalemate test */
7976                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7977                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7978                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7979                      if(canAdjudicate && appData.checkMates) {
7980                          if(engineOpponent)
7981                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7982                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7983                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7984                          return 1;
7985                      }
7986                 }
7987
7988                 /* Bare King in Shatranj (loses) or Losers (wins) */
7989                 if( nrW == 1 || nrB == 1) {
7990                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7991                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7992                      if(canAdjudicate && appData.checkMates) {
7993                          if(engineOpponent)
7994                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7995                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7996                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7997                          return 1;
7998                      }
7999                   } else
8000                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8001                   {    /* bare King */
8002                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8003                         if(canAdjudicate && appData.checkMates) {
8004                             /* but only adjudicate if adjudication enabled */
8005                             if(engineOpponent)
8006                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8007                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8008                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8009                             return 1;
8010                         }
8011                   }
8012                 } else bare = 1;
8013
8014
8015             // don't wait for engine to announce game end if we can judge ourselves
8016             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8017               case MT_CHECK:
8018                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8019                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8020                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8021                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8022                             checkCnt++;
8023                         if(checkCnt >= 2) {
8024                             reason = "Xboard adjudication: 3rd check";
8025                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8026                             break;
8027                         }
8028                     }
8029                 }
8030               case MT_NONE:
8031               default:
8032                 break;
8033               case MT_STALEMATE:
8034               case MT_STAINMATE:
8035                 reason = "Xboard adjudication: Stalemate";
8036                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8037                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8038                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8039                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8040                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8041                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8042                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8043                                                                         EP_CHECKMATE : EP_WINS);
8044                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8045                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8046                 }
8047                 break;
8048               case MT_CHECKMATE:
8049                 reason = "Xboard adjudication: Checkmate";
8050                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8051                 if(gameInfo.variant == VariantShogi) {
8052                     if(forwardMostMove > backwardMostMove
8053                        && moveList[forwardMostMove-1][1] == '@'
8054                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8055                         reason = "XBoard adjudication: pawn-drop mate";
8056                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8057                     }
8058                 }
8059                 break;
8060             }
8061
8062                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8063                     case EP_STALEMATE:
8064                         result = GameIsDrawn; break;
8065                     case EP_CHECKMATE:
8066                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8067                     case EP_WINS:
8068                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8069                     default:
8070                         result = EndOfFile;
8071                 }
8072                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8073                     if(engineOpponent)
8074                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8075                     GameEnds( result, reason, GE_XBOARD );
8076                     return 1;
8077                 }
8078
8079                 /* Next absolutely insufficient mating material. */
8080                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8081                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8082                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8083
8084                      /* always flag draws, for judging claims */
8085                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8086
8087                      if(canAdjudicate && appData.materialDraws) {
8088                          /* but only adjudicate them if adjudication enabled */
8089                          if(engineOpponent) {
8090                            SendToProgram("force\n", engineOpponent); // suppress reply
8091                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8092                          }
8093                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8094                          return 1;
8095                      }
8096                 }
8097
8098                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8099                 if(gameInfo.variant == VariantXiangqi ?
8100                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8101                  : nrW + nrB == 4 &&
8102                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8103                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8104                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8105                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8106                    ) ) {
8107                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8108                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8109                           if(engineOpponent) {
8110                             SendToProgram("force\n", engineOpponent); // suppress reply
8111                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8112                           }
8113                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8114                           return 1;
8115                      }
8116                 } else moveCount = 6;
8117             }
8118
8119         // Repetition draws and 50-move rule can be applied independently of legality testing
8120
8121                 /* Check for rep-draws */
8122                 count = 0;
8123                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8124                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8125                 for(k = forwardMostMove-2;
8126                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8127                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8128                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8129                     k-=2)
8130                 {   int rights=0;
8131                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8132                         /* compare castling rights */
8133                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8134                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8135                                 rights++; /* King lost rights, while rook still had them */
8136                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8137                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8138                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8139                                    rights++; /* but at least one rook lost them */
8140                         }
8141                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8142                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8143                                 rights++;
8144                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8145                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8146                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8147                                    rights++;
8148                         }
8149                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8150                             && appData.drawRepeats > 1) {
8151                              /* adjudicate after user-specified nr of repeats */
8152                              int result = GameIsDrawn;
8153                              char *details = "XBoard adjudication: repetition draw";
8154                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8155                                 // [HGM] xiangqi: check for forbidden perpetuals
8156                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8157                                 for(m=forwardMostMove; m>k; m-=2) {
8158                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8159                                         ourPerpetual = 0; // the current mover did not always check
8160                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8161                                         hisPerpetual = 0; // the opponent did not always check
8162                                 }
8163                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8164                                                                         ourPerpetual, hisPerpetual);
8165                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8166                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8167                                     details = "Xboard adjudication: perpetual checking";
8168                                 } else
8169                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8170                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8171                                 } else
8172                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8173                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8174                                         result = BlackWins;
8175                                         details = "Xboard adjudication: repetition";
8176                                     }
8177                                 } else // it must be XQ
8178                                 // Now check for perpetual chases
8179                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8180                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8181                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8182                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8183                                         static char resdet[MSG_SIZ];
8184                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8185                                         details = resdet;
8186                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8187                                     } else
8188                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8189                                         break; // Abort repetition-checking loop.
8190                                 }
8191                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8192                              }
8193                              if(engineOpponent) {
8194                                SendToProgram("force\n", engineOpponent); // suppress reply
8195                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8196                              }
8197                              GameEnds( result, details, GE_XBOARD );
8198                              return 1;
8199                         }
8200                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8201                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8202                     }
8203                 }
8204
8205                 /* Now we test for 50-move draws. Determine ply count */
8206                 count = forwardMostMove;
8207                 /* look for last irreversble move */
8208                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8209                     count--;
8210                 /* if we hit starting position, add initial plies */
8211                 if( count == backwardMostMove )
8212                     count -= initialRulePlies;
8213                 count = forwardMostMove - count;
8214                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8215                         // adjust reversible move counter for checks in Xiangqi
8216                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8217                         if(i < backwardMostMove) i = backwardMostMove;
8218                         while(i <= forwardMostMove) {
8219                                 lastCheck = inCheck; // check evasion does not count
8220                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8221                                 if(inCheck || lastCheck) count--; // check does not count
8222                                 i++;
8223                         }
8224                 }
8225                 if( count >= 100)
8226                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8227                          /* this is used to judge if draw claims are legal */
8228                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8229                          if(engineOpponent) {
8230                            SendToProgram("force\n", engineOpponent); // suppress reply
8231                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8232                          }
8233                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8234                          return 1;
8235                 }
8236
8237                 /* if draw offer is pending, treat it as a draw claim
8238                  * when draw condition present, to allow engines a way to
8239                  * claim draws before making their move to avoid a race
8240                  * condition occurring after their move
8241                  */
8242                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8243                          char *p = NULL;
8244                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8245                              p = "Draw claim: 50-move rule";
8246                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8247                              p = "Draw claim: 3-fold repetition";
8248                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8249                              p = "Draw claim: insufficient mating material";
8250                          if( p != NULL && canAdjudicate) {
8251                              if(engineOpponent) {
8252                                SendToProgram("force\n", engineOpponent); // suppress reply
8253                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8254                              }
8255                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8256                              return 1;
8257                          }
8258                 }
8259
8260                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8261                     if(engineOpponent) {
8262                       SendToProgram("force\n", engineOpponent); // suppress reply
8263                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8264                     }
8265                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8266                     return 1;
8267                 }
8268         return 0;
8269 }
8270
8271 char *
8272 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8273 {   // [HGM] book: this routine intercepts moves to simulate book replies
8274     char *bookHit = NULL;
8275
8276     //first determine if the incoming move brings opponent into his book
8277     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8278         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8279     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8280     if(bookHit != NULL && !cps->bookSuspend) {
8281         // make sure opponent is not going to reply after receiving move to book position
8282         SendToProgram("force\n", cps);
8283         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8284     }
8285     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8286     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8287     // now arrange restart after book miss
8288     if(bookHit) {
8289         // after a book hit we never send 'go', and the code after the call to this routine
8290         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8291         char buf[MSG_SIZ], *move = bookHit;
8292         if(cps->useSAN) {
8293             int fromX, fromY, toX, toY;
8294             char promoChar;
8295             ChessMove moveType;
8296             move = buf + 30;
8297             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8298                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8299                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8300                                     PosFlags(forwardMostMove),
8301                                     fromY, fromX, toY, toX, promoChar, move);
8302             } else {
8303                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8304                 bookHit = NULL;
8305             }
8306         }
8307         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8308         SendToProgram(buf, cps);
8309         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8310     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8311         SendToProgram("go\n", cps);
8312         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8313     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8314         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8315             SendToProgram("go\n", cps);
8316         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8317     }
8318     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8319 }
8320
8321 int
8322 LoadError (char *errmess, ChessProgramState *cps)
8323 {   // unloads engine and switches back to -ncp mode if it was first
8324     if(cps->initDone) return FALSE;
8325     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8326     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8327     cps->pr = NoProc;
8328     if(cps == &first) {
8329         appData.noChessProgram = TRUE;
8330         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8331         gameMode = BeginningOfGame; ModeHighlight();
8332         SetNCPMode();
8333     }
8334     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8335     DisplayMessage("", ""); // erase waiting message
8336     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8337     return TRUE;
8338 }
8339
8340 char *savedMessage;
8341 ChessProgramState *savedState;
8342 void
8343 DeferredBookMove (void)
8344 {
8345         if(savedState->lastPing != savedState->lastPong)
8346                     ScheduleDelayedEvent(DeferredBookMove, 10);
8347         else
8348         HandleMachineMove(savedMessage, savedState);
8349 }
8350
8351 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8352 static ChessProgramState *stalledEngine;
8353 static char stashedInputMove[MSG_SIZ];
8354
8355 void
8356 HandleMachineMove (char *message, ChessProgramState *cps)
8357 {
8358     static char firstLeg[20];
8359     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8360     char realname[MSG_SIZ];
8361     int fromX, fromY, toX, toY;
8362     ChessMove moveType;
8363     char promoChar, roar;
8364     char *p, *pv=buf1;
8365     int machineWhite, oldError;
8366     char *bookHit;
8367
8368     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8369         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8370         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8371             DisplayError(_("Invalid pairing from pairing engine"), 0);
8372             return;
8373         }
8374         pairingReceived = 1;
8375         NextMatchGame();
8376         return; // Skim the pairing messages here.
8377     }
8378
8379     oldError = cps->userError; cps->userError = 0;
8380
8381 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8382     /*
8383      * Kludge to ignore BEL characters
8384      */
8385     while (*message == '\007') message++;
8386
8387     /*
8388      * [HGM] engine debug message: ignore lines starting with '#' character
8389      */
8390     if(cps->debug && *message == '#') return;
8391
8392     /*
8393      * Look for book output
8394      */
8395     if (cps == &first && bookRequested) {
8396         if (message[0] == '\t' || message[0] == ' ') {
8397             /* Part of the book output is here; append it */
8398             strcat(bookOutput, message);
8399             strcat(bookOutput, "  \n");
8400             return;
8401         } else if (bookOutput[0] != NULLCHAR) {
8402             /* All of book output has arrived; display it */
8403             char *p = bookOutput;
8404             while (*p != NULLCHAR) {
8405                 if (*p == '\t') *p = ' ';
8406                 p++;
8407             }
8408             DisplayInformation(bookOutput);
8409             bookRequested = FALSE;
8410             /* Fall through to parse the current output */
8411         }
8412     }
8413
8414     /*
8415      * Look for machine move.
8416      */
8417     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8418         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8419     {
8420         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8421             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8422             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8423             stalledEngine = cps;
8424             if(appData.ponderNextMove) { // bring opponent out of ponder
8425                 if(gameMode == TwoMachinesPlay) {
8426                     if(cps->other->pause)
8427                         PauseEngine(cps->other);
8428                     else
8429                         SendToProgram("easy\n", cps->other);
8430                 }
8431             }
8432             StopClocks();
8433             return;
8434         }
8435
8436         /* This method is only useful on engines that support ping */
8437         if (cps->lastPing != cps->lastPong) {
8438           if (gameMode == BeginningOfGame) {
8439             /* Extra move from before last new; ignore */
8440             if (appData.debugMode) {
8441                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8442             }
8443           } else {
8444             if (appData.debugMode) {
8445                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8446                         cps->which, gameMode);
8447             }
8448
8449             SendToProgram("undo\n", cps);
8450           }
8451           return;
8452         }
8453
8454         switch (gameMode) {
8455           case BeginningOfGame:
8456             /* Extra move from before last reset; ignore */
8457             if (appData.debugMode) {
8458                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8459             }
8460             return;
8461
8462           case EndOfGame:
8463           case IcsIdle:
8464           default:
8465             /* Extra move after we tried to stop.  The mode test is
8466                not a reliable way of detecting this problem, but it's
8467                the best we can do on engines that don't support ping.
8468             */
8469             if (appData.debugMode) {
8470                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8471                         cps->which, gameMode);
8472             }
8473             SendToProgram("undo\n", cps);
8474             return;
8475
8476           case MachinePlaysWhite:
8477           case IcsPlayingWhite:
8478             machineWhite = TRUE;
8479             break;
8480
8481           case MachinePlaysBlack:
8482           case IcsPlayingBlack:
8483             machineWhite = FALSE;
8484             break;
8485
8486           case TwoMachinesPlay:
8487             machineWhite = (cps->twoMachinesColor[0] == 'w');
8488             break;
8489         }
8490         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8491             if (appData.debugMode) {
8492                 fprintf(debugFP,
8493                         "Ignoring move out of turn by %s, gameMode %d"
8494                         ", forwardMost %d\n",
8495                         cps->which, gameMode, forwardMostMove);
8496             }
8497             return;
8498         }
8499
8500         if(cps->alphaRank) AlphaRank(machineMove, 4);
8501
8502         // [HGM] lion: (some very limited) support for Alien protocol
8503         killX = killY = -1;
8504         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8505             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8506             return;
8507         } else if(firstLeg[0]) { // there was a previous leg;
8508             // only support case where same piece makes two step (and don't even test that!)
8509             char buf[20], *p = machineMove+1, *q = buf+1, f;
8510             safeStrCpy(buf, machineMove, 20);
8511             while(isdigit(*q)) q++; // find start of to-square
8512             safeStrCpy(machineMove, firstLeg, 20);
8513             while(isdigit(*p)) p++;
8514             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8515             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8516             firstLeg[0] = NULLCHAR;
8517         }
8518
8519         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8520                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8521             /* Machine move could not be parsed; ignore it. */
8522           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8523                     machineMove, _(cps->which));
8524             DisplayMoveError(buf1);
8525             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8526                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8527             if (gameMode == TwoMachinesPlay) {
8528               GameEnds(machineWhite ? BlackWins : WhiteWins,
8529                        buf1, GE_XBOARD);
8530             }
8531             return;
8532         }
8533
8534         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8535         /* So we have to redo legality test with true e.p. status here,  */
8536         /* to make sure an illegal e.p. capture does not slip through,   */
8537         /* to cause a forfeit on a justified illegal-move complaint      */
8538         /* of the opponent.                                              */
8539         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8540            ChessMove moveType;
8541            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8542                              fromY, fromX, toY, toX, promoChar);
8543             if(moveType == IllegalMove) {
8544               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8545                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8546                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8547                            buf1, GE_XBOARD);
8548                 return;
8549            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8550            /* [HGM] Kludge to handle engines that send FRC-style castling
8551               when they shouldn't (like TSCP-Gothic) */
8552            switch(moveType) {
8553              case WhiteASideCastleFR:
8554              case BlackASideCastleFR:
8555                toX+=2;
8556                currentMoveString[2]++;
8557                break;
8558              case WhiteHSideCastleFR:
8559              case BlackHSideCastleFR:
8560                toX--;
8561                currentMoveString[2]--;
8562                break;
8563              default: ; // nothing to do, but suppresses warning of pedantic compilers
8564            }
8565         }
8566         hintRequested = FALSE;
8567         lastHint[0] = NULLCHAR;
8568         bookRequested = FALSE;
8569         /* Program may be pondering now */
8570         cps->maybeThinking = TRUE;
8571         if (cps->sendTime == 2) cps->sendTime = 1;
8572         if (cps->offeredDraw) cps->offeredDraw--;
8573
8574         /* [AS] Save move info*/
8575         pvInfoList[ forwardMostMove ].score = programStats.score;
8576         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8577         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8578
8579         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8580
8581         /* Test suites abort the 'game' after one move */
8582         if(*appData.finger) {
8583            static FILE *f;
8584            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8585            if(!f) f = fopen(appData.finger, "w");
8586            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8587            else { DisplayFatalError("Bad output file", errno, 0); return; }
8588            free(fen);
8589            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8590         }
8591
8592         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8593         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8594             int count = 0;
8595
8596             while( count < adjudicateLossPlies ) {
8597                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8598
8599                 if( count & 1 ) {
8600                     score = -score; /* Flip score for winning side */
8601                 }
8602
8603                 if( score > adjudicateLossThreshold ) {
8604                     break;
8605                 }
8606
8607                 count++;
8608             }
8609
8610             if( count >= adjudicateLossPlies ) {
8611                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8612
8613                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8614                     "Xboard adjudication",
8615                     GE_XBOARD );
8616
8617                 return;
8618             }
8619         }
8620
8621         if(Adjudicate(cps)) {
8622             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8623             return; // [HGM] adjudicate: for all automatic game ends
8624         }
8625
8626 #if ZIPPY
8627         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8628             first.initDone) {
8629           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8630                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8631                 SendToICS("draw ");
8632                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8633           }
8634           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8635           ics_user_moved = 1;
8636           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8637                 char buf[3*MSG_SIZ];
8638
8639                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8640                         programStats.score / 100.,
8641                         programStats.depth,
8642                         programStats.time / 100.,
8643                         (unsigned int)programStats.nodes,
8644                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8645                         programStats.movelist);
8646                 SendToICS(buf);
8647 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8648           }
8649         }
8650 #endif
8651
8652         /* [AS] Clear stats for next move */
8653         ClearProgramStats();
8654         thinkOutput[0] = NULLCHAR;
8655         hiddenThinkOutputState = 0;
8656
8657         bookHit = NULL;
8658         if (gameMode == TwoMachinesPlay) {
8659             /* [HGM] relaying draw offers moved to after reception of move */
8660             /* and interpreting offer as claim if it brings draw condition */
8661             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8662                 SendToProgram("draw\n", cps->other);
8663             }
8664             if (cps->other->sendTime) {
8665                 SendTimeRemaining(cps->other,
8666                                   cps->other->twoMachinesColor[0] == 'w');
8667             }
8668             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8669             if (firstMove && !bookHit) {
8670                 firstMove = FALSE;
8671                 if (cps->other->useColors) {
8672                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8673                 }
8674                 SendToProgram("go\n", cps->other);
8675             }
8676             cps->other->maybeThinking = TRUE;
8677         }
8678
8679         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8680
8681         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8682
8683         if (!pausing && appData.ringBellAfterMoves) {
8684             if(!roar) RingBell();
8685         }
8686
8687         /*
8688          * Reenable menu items that were disabled while
8689          * machine was thinking
8690          */
8691         if (gameMode != TwoMachinesPlay)
8692             SetUserThinkingEnables();
8693
8694         // [HGM] book: after book hit opponent has received move and is now in force mode
8695         // force the book reply into it, and then fake that it outputted this move by jumping
8696         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8697         if(bookHit) {
8698                 static char bookMove[MSG_SIZ]; // a bit generous?
8699
8700                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8701                 strcat(bookMove, bookHit);
8702                 message = bookMove;
8703                 cps = cps->other;
8704                 programStats.nodes = programStats.depth = programStats.time =
8705                 programStats.score = programStats.got_only_move = 0;
8706                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8707
8708                 if(cps->lastPing != cps->lastPong) {
8709                     savedMessage = message; // args for deferred call
8710                     savedState = cps;
8711                     ScheduleDelayedEvent(DeferredBookMove, 10);
8712                     return;
8713                 }
8714                 goto FakeBookMove;
8715         }
8716
8717         return;
8718     }
8719
8720     /* Set special modes for chess engines.  Later something general
8721      *  could be added here; for now there is just one kludge feature,
8722      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8723      *  when "xboard" is given as an interactive command.
8724      */
8725     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8726         cps->useSigint = FALSE;
8727         cps->useSigterm = FALSE;
8728     }
8729     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8730       ParseFeatures(message+8, cps);
8731       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8732     }
8733
8734     if (!strncmp(message, "setup ", 6) && 
8735         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8736           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8737                                         ) { // [HGM] allow first engine to define opening position
8738       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8739       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8740       *buf = NULLCHAR;
8741       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8742       if(startedFromSetupPosition) return;
8743       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8744       if(dummy >= 3) {
8745         while(message[s] && message[s++] != ' ');
8746         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8747            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8748             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8749             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8750           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8751           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8752         }
8753       }
8754       ParseFEN(boards[0], &dummy, message+s, FALSE);
8755       DrawPosition(TRUE, boards[0]);
8756       startedFromSetupPosition = TRUE;
8757       return;
8758     }
8759     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8760      * want this, I was asked to put it in, and obliged.
8761      */
8762     if (!strncmp(message, "setboard ", 9)) {
8763         Board initial_position;
8764
8765         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8766
8767         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8768             DisplayError(_("Bad FEN received from engine"), 0);
8769             return ;
8770         } else {
8771            Reset(TRUE, FALSE);
8772            CopyBoard(boards[0], initial_position);
8773            initialRulePlies = FENrulePlies;
8774            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8775            else gameMode = MachinePlaysBlack;
8776            DrawPosition(FALSE, boards[currentMove]);
8777         }
8778         return;
8779     }
8780
8781     /*
8782      * Look for communication commands
8783      */
8784     if (!strncmp(message, "telluser ", 9)) {
8785         if(message[9] == '\\' && message[10] == '\\')
8786             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8787         PlayTellSound();
8788         DisplayNote(message + 9);
8789         return;
8790     }
8791     if (!strncmp(message, "tellusererror ", 14)) {
8792         cps->userError = 1;
8793         if(message[14] == '\\' && message[15] == '\\')
8794             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8795         PlayTellSound();
8796         DisplayError(message + 14, 0);
8797         return;
8798     }
8799     if (!strncmp(message, "tellopponent ", 13)) {
8800       if (appData.icsActive) {
8801         if (loggedOn) {
8802           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8803           SendToICS(buf1);
8804         }
8805       } else {
8806         DisplayNote(message + 13);
8807       }
8808       return;
8809     }
8810     if (!strncmp(message, "tellothers ", 11)) {
8811       if (appData.icsActive) {
8812         if (loggedOn) {
8813           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8814           SendToICS(buf1);
8815         }
8816       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8817       return;
8818     }
8819     if (!strncmp(message, "tellall ", 8)) {
8820       if (appData.icsActive) {
8821         if (loggedOn) {
8822           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8823           SendToICS(buf1);
8824         }
8825       } else {
8826         DisplayNote(message + 8);
8827       }
8828       return;
8829     }
8830     if (strncmp(message, "warning", 7) == 0) {
8831         /* Undocumented feature, use tellusererror in new code */
8832         DisplayError(message, 0);
8833         return;
8834     }
8835     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8836         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8837         strcat(realname, " query");
8838         AskQuestion(realname, buf2, buf1, cps->pr);
8839         return;
8840     }
8841     /* Commands from the engine directly to ICS.  We don't allow these to be
8842      *  sent until we are logged on. Crafty kibitzes have been known to
8843      *  interfere with the login process.
8844      */
8845     if (loggedOn) {
8846         if (!strncmp(message, "tellics ", 8)) {
8847             SendToICS(message + 8);
8848             SendToICS("\n");
8849             return;
8850         }
8851         if (!strncmp(message, "tellicsnoalias ", 15)) {
8852             SendToICS(ics_prefix);
8853             SendToICS(message + 15);
8854             SendToICS("\n");
8855             return;
8856         }
8857         /* The following are for backward compatibility only */
8858         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8859             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8860             SendToICS(ics_prefix);
8861             SendToICS(message);
8862             SendToICS("\n");
8863             return;
8864         }
8865     }
8866     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8867         if(initPing == cps->lastPong) {
8868             if(gameInfo.variant == VariantUnknown) {
8869                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8870                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8871                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8872             }
8873             initPing = -1;
8874         }
8875         return;
8876     }
8877     if(!strncmp(message, "highlight ", 10)) {
8878         if(appData.testLegality && appData.markers) return;
8879         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8880         return;
8881     }
8882     if(!strncmp(message, "click ", 6)) {
8883         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8884         if(appData.testLegality || !appData.oneClick) return;
8885         sscanf(message+6, "%c%d%c", &f, &y, &c);
8886         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8887         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8888         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8889         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8890         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8891         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8892             LeftClick(Release, lastLeftX, lastLeftY);
8893         controlKey  = (c == ',');
8894         LeftClick(Press, x, y);
8895         LeftClick(Release, x, y);
8896         first.highlight = f;
8897         return;
8898     }
8899     /*
8900      * If the move is illegal, cancel it and redraw the board.
8901      * Also deal with other error cases.  Matching is rather loose
8902      * here to accommodate engines written before the spec.
8903      */
8904     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8905         strncmp(message, "Error", 5) == 0) {
8906         if (StrStr(message, "name") ||
8907             StrStr(message, "rating") || StrStr(message, "?") ||
8908             StrStr(message, "result") || StrStr(message, "board") ||
8909             StrStr(message, "bk") || StrStr(message, "computer") ||
8910             StrStr(message, "variant") || StrStr(message, "hint") ||
8911             StrStr(message, "random") || StrStr(message, "depth") ||
8912             StrStr(message, "accepted")) {
8913             return;
8914         }
8915         if (StrStr(message, "protover")) {
8916           /* Program is responding to input, so it's apparently done
8917              initializing, and this error message indicates it is
8918              protocol version 1.  So we don't need to wait any longer
8919              for it to initialize and send feature commands. */
8920           FeatureDone(cps, 1);
8921           cps->protocolVersion = 1;
8922           return;
8923         }
8924         cps->maybeThinking = FALSE;
8925
8926         if (StrStr(message, "draw")) {
8927             /* Program doesn't have "draw" command */
8928             cps->sendDrawOffers = 0;
8929             return;
8930         }
8931         if (cps->sendTime != 1 &&
8932             (StrStr(message, "time") || StrStr(message, "otim"))) {
8933           /* Program apparently doesn't have "time" or "otim" command */
8934           cps->sendTime = 0;
8935           return;
8936         }
8937         if (StrStr(message, "analyze")) {
8938             cps->analysisSupport = FALSE;
8939             cps->analyzing = FALSE;
8940 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8941             EditGameEvent(); // [HGM] try to preserve loaded game
8942             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8943             DisplayError(buf2, 0);
8944             return;
8945         }
8946         if (StrStr(message, "(no matching move)st")) {
8947           /* Special kludge for GNU Chess 4 only */
8948           cps->stKludge = TRUE;
8949           SendTimeControl(cps, movesPerSession, timeControl,
8950                           timeIncrement, appData.searchDepth,
8951                           searchTime);
8952           return;
8953         }
8954         if (StrStr(message, "(no matching move)sd")) {
8955           /* Special kludge for GNU Chess 4 only */
8956           cps->sdKludge = TRUE;
8957           SendTimeControl(cps, movesPerSession, timeControl,
8958                           timeIncrement, appData.searchDepth,
8959                           searchTime);
8960           return;
8961         }
8962         if (!StrStr(message, "llegal")) {
8963             return;
8964         }
8965         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8966             gameMode == IcsIdle) return;
8967         if (forwardMostMove <= backwardMostMove) return;
8968         if (pausing) PauseEvent();
8969       if(appData.forceIllegal) {
8970             // [HGM] illegal: machine refused move; force position after move into it
8971           SendToProgram("force\n", cps);
8972           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8973                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8974                 // when black is to move, while there might be nothing on a2 or black
8975                 // might already have the move. So send the board as if white has the move.
8976                 // But first we must change the stm of the engine, as it refused the last move
8977                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8978                 if(WhiteOnMove(forwardMostMove)) {
8979                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8980                     SendBoard(cps, forwardMostMove); // kludgeless board
8981                 } else {
8982                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8983                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8984                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8985                 }
8986           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8987             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8988                  gameMode == TwoMachinesPlay)
8989               SendToProgram("go\n", cps);
8990             return;
8991       } else
8992         if (gameMode == PlayFromGameFile) {
8993             /* Stop reading this game file */
8994             gameMode = EditGame;
8995             ModeHighlight();
8996         }
8997         /* [HGM] illegal-move claim should forfeit game when Xboard */
8998         /* only passes fully legal moves                            */
8999         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9000             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9001                                 "False illegal-move claim", GE_XBOARD );
9002             return; // do not take back move we tested as valid
9003         }
9004         currentMove = forwardMostMove-1;
9005         DisplayMove(currentMove-1); /* before DisplayMoveError */
9006         SwitchClocks(forwardMostMove-1); // [HGM] race
9007         DisplayBothClocks();
9008         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9009                 parseList[currentMove], _(cps->which));
9010         DisplayMoveError(buf1);
9011         DrawPosition(FALSE, boards[currentMove]);
9012
9013         SetUserThinkingEnables();
9014         return;
9015     }
9016     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9017         /* Program has a broken "time" command that
9018            outputs a string not ending in newline.
9019            Don't use it. */
9020         cps->sendTime = 0;
9021     }
9022
9023     /*
9024      * If chess program startup fails, exit with an error message.
9025      * Attempts to recover here are futile. [HGM] Well, we try anyway
9026      */
9027     if ((StrStr(message, "unknown host") != NULL)
9028         || (StrStr(message, "No remote directory") != NULL)
9029         || (StrStr(message, "not found") != NULL)
9030         || (StrStr(message, "No such file") != NULL)
9031         || (StrStr(message, "can't alloc") != NULL)
9032         || (StrStr(message, "Permission denied") != NULL)) {
9033
9034         cps->maybeThinking = FALSE;
9035         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9036                 _(cps->which), cps->program, cps->host, message);
9037         RemoveInputSource(cps->isr);
9038         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9039             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9040             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9041         }
9042         return;
9043     }
9044
9045     /*
9046      * Look for hint output
9047      */
9048     if (sscanf(message, "Hint: %s", buf1) == 1) {
9049         if (cps == &first && hintRequested) {
9050             hintRequested = FALSE;
9051             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9052                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9053                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9054                                     PosFlags(forwardMostMove),
9055                                     fromY, fromX, toY, toX, promoChar, buf1);
9056                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9057                 DisplayInformation(buf2);
9058             } else {
9059                 /* Hint move could not be parsed!? */
9060               snprintf(buf2, sizeof(buf2),
9061                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9062                         buf1, _(cps->which));
9063                 DisplayError(buf2, 0);
9064             }
9065         } else {
9066           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9067         }
9068         return;
9069     }
9070
9071     /*
9072      * Ignore other messages if game is not in progress
9073      */
9074     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9075         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9076
9077     /*
9078      * look for win, lose, draw, or draw offer
9079      */
9080     if (strncmp(message, "1-0", 3) == 0) {
9081         char *p, *q, *r = "";
9082         p = strchr(message, '{');
9083         if (p) {
9084             q = strchr(p, '}');
9085             if (q) {
9086                 *q = NULLCHAR;
9087                 r = p + 1;
9088             }
9089         }
9090         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9091         return;
9092     } else if (strncmp(message, "0-1", 3) == 0) {
9093         char *p, *q, *r = "";
9094         p = strchr(message, '{');
9095         if (p) {
9096             q = strchr(p, '}');
9097             if (q) {
9098                 *q = NULLCHAR;
9099                 r = p + 1;
9100             }
9101         }
9102         /* Kludge for Arasan 4.1 bug */
9103         if (strcmp(r, "Black resigns") == 0) {
9104             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9105             return;
9106         }
9107         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9108         return;
9109     } else if (strncmp(message, "1/2", 3) == 0) {
9110         char *p, *q, *r = "";
9111         p = strchr(message, '{');
9112         if (p) {
9113             q = strchr(p, '}');
9114             if (q) {
9115                 *q = NULLCHAR;
9116                 r = p + 1;
9117             }
9118         }
9119
9120         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9121         return;
9122
9123     } else if (strncmp(message, "White resign", 12) == 0) {
9124         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9125         return;
9126     } else if (strncmp(message, "Black resign", 12) == 0) {
9127         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9128         return;
9129     } else if (strncmp(message, "White matches", 13) == 0 ||
9130                strncmp(message, "Black matches", 13) == 0   ) {
9131         /* [HGM] ignore GNUShogi noises */
9132         return;
9133     } else if (strncmp(message, "White", 5) == 0 &&
9134                message[5] != '(' &&
9135                StrStr(message, "Black") == NULL) {
9136         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9137         return;
9138     } else if (strncmp(message, "Black", 5) == 0 &&
9139                message[5] != '(') {
9140         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9141         return;
9142     } else if (strcmp(message, "resign") == 0 ||
9143                strcmp(message, "computer resigns") == 0) {
9144         switch (gameMode) {
9145           case MachinePlaysBlack:
9146           case IcsPlayingBlack:
9147             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9148             break;
9149           case MachinePlaysWhite:
9150           case IcsPlayingWhite:
9151             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9152             break;
9153           case TwoMachinesPlay:
9154             if (cps->twoMachinesColor[0] == 'w')
9155               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9156             else
9157               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9158             break;
9159           default:
9160             /* can't happen */
9161             break;
9162         }
9163         return;
9164     } else if (strncmp(message, "opponent mates", 14) == 0) {
9165         switch (gameMode) {
9166           case MachinePlaysBlack:
9167           case IcsPlayingBlack:
9168             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9169             break;
9170           case MachinePlaysWhite:
9171           case IcsPlayingWhite:
9172             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9173             break;
9174           case TwoMachinesPlay:
9175             if (cps->twoMachinesColor[0] == 'w')
9176               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9177             else
9178               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9179             break;
9180           default:
9181             /* can't happen */
9182             break;
9183         }
9184         return;
9185     } else if (strncmp(message, "computer mates", 14) == 0) {
9186         switch (gameMode) {
9187           case MachinePlaysBlack:
9188           case IcsPlayingBlack:
9189             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9190             break;
9191           case MachinePlaysWhite:
9192           case IcsPlayingWhite:
9193             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9194             break;
9195           case TwoMachinesPlay:
9196             if (cps->twoMachinesColor[0] == 'w')
9197               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9198             else
9199               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9200             break;
9201           default:
9202             /* can't happen */
9203             break;
9204         }
9205         return;
9206     } else if (strncmp(message, "checkmate", 9) == 0) {
9207         if (WhiteOnMove(forwardMostMove)) {
9208             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9209         } else {
9210             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9211         }
9212         return;
9213     } else if (strstr(message, "Draw") != NULL ||
9214                strstr(message, "game is a draw") != NULL) {
9215         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9216         return;
9217     } else if (strstr(message, "offer") != NULL &&
9218                strstr(message, "draw") != NULL) {
9219 #if ZIPPY
9220         if (appData.zippyPlay && first.initDone) {
9221             /* Relay offer to ICS */
9222             SendToICS(ics_prefix);
9223             SendToICS("draw\n");
9224         }
9225 #endif
9226         cps->offeredDraw = 2; /* valid until this engine moves twice */
9227         if (gameMode == TwoMachinesPlay) {
9228             if (cps->other->offeredDraw) {
9229                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9230             /* [HGM] in two-machine mode we delay relaying draw offer      */
9231             /* until after we also have move, to see if it is really claim */
9232             }
9233         } else if (gameMode == MachinePlaysWhite ||
9234                    gameMode == MachinePlaysBlack) {
9235           if (userOfferedDraw) {
9236             DisplayInformation(_("Machine accepts your draw offer"));
9237             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9238           } else {
9239             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9240           }
9241         }
9242     }
9243
9244
9245     /*
9246      * Look for thinking output
9247      */
9248     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9249           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9250                                 ) {
9251         int plylev, mvleft, mvtot, curscore, time;
9252         char mvname[MOVE_LEN];
9253         u64 nodes; // [DM]
9254         char plyext;
9255         int ignore = FALSE;
9256         int prefixHint = FALSE;
9257         mvname[0] = NULLCHAR;
9258
9259         switch (gameMode) {
9260           case MachinePlaysBlack:
9261           case IcsPlayingBlack:
9262             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9263             break;
9264           case MachinePlaysWhite:
9265           case IcsPlayingWhite:
9266             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9267             break;
9268           case AnalyzeMode:
9269           case AnalyzeFile:
9270             break;
9271           case IcsObserving: /* [DM] icsEngineAnalyze */
9272             if (!appData.icsEngineAnalyze) ignore = TRUE;
9273             break;
9274           case TwoMachinesPlay:
9275             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9276                 ignore = TRUE;
9277             }
9278             break;
9279           default:
9280             ignore = TRUE;
9281             break;
9282         }
9283
9284         if (!ignore) {
9285             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9286             buf1[0] = NULLCHAR;
9287             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9288                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9289
9290                 if (plyext != ' ' && plyext != '\t') {
9291                     time *= 100;
9292                 }
9293
9294                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9295                 if( cps->scoreIsAbsolute &&
9296                     ( gameMode == MachinePlaysBlack ||
9297                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9298                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9299                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9300                      !WhiteOnMove(currentMove)
9301                     ) )
9302                 {
9303                     curscore = -curscore;
9304                 }
9305
9306                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9307
9308                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9309                         char buf[MSG_SIZ];
9310                         FILE *f;
9311                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9312                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9313                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9314                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9315                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9316                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9317                                 fclose(f);
9318                         }
9319                         else
9320                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9321                           DisplayError(_("failed writing PV"), 0);
9322                 }
9323
9324                 tempStats.depth = plylev;
9325                 tempStats.nodes = nodes;
9326                 tempStats.time = time;
9327                 tempStats.score = curscore;
9328                 tempStats.got_only_move = 0;
9329
9330                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9331                         int ticklen;
9332
9333                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9334                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9335                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9336                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9337                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9338                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9339                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9340                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9341                 }
9342
9343                 /* Buffer overflow protection */
9344                 if (pv[0] != NULLCHAR) {
9345                     if (strlen(pv) >= sizeof(tempStats.movelist)
9346                         && appData.debugMode) {
9347                         fprintf(debugFP,
9348                                 "PV is too long; using the first %u bytes.\n",
9349                                 (unsigned) sizeof(tempStats.movelist) - 1);
9350                     }
9351
9352                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9353                 } else {
9354                     sprintf(tempStats.movelist, " no PV\n");
9355                 }
9356
9357                 if (tempStats.seen_stat) {
9358                     tempStats.ok_to_send = 1;
9359                 }
9360
9361                 if (strchr(tempStats.movelist, '(') != NULL) {
9362                     tempStats.line_is_book = 1;
9363                     tempStats.nr_moves = 0;
9364                     tempStats.moves_left = 0;
9365                 } else {
9366                     tempStats.line_is_book = 0;
9367                 }
9368
9369                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9370                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9371
9372                 SendProgramStatsToFrontend( cps, &tempStats );
9373
9374                 /*
9375                     [AS] Protect the thinkOutput buffer from overflow... this
9376                     is only useful if buf1 hasn't overflowed first!
9377                 */
9378                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9379                          plylev,
9380                          (gameMode == TwoMachinesPlay ?
9381                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9382                          ((double) curscore) / 100.0,
9383                          prefixHint ? lastHint : "",
9384                          prefixHint ? " " : "" );
9385
9386                 if( buf1[0] != NULLCHAR ) {
9387                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9388
9389                     if( strlen(pv) > max_len ) {
9390                         if( appData.debugMode) {
9391                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9392                         }
9393                         pv[max_len+1] = '\0';
9394                     }
9395
9396                     strcat( thinkOutput, pv);
9397                 }
9398
9399                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9400                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9401                     DisplayMove(currentMove - 1);
9402                 }
9403                 return;
9404
9405             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9406                 /* crafty (9.25+) says "(only move) <move>"
9407                  * if there is only 1 legal move
9408                  */
9409                 sscanf(p, "(only move) %s", buf1);
9410                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9411                 sprintf(programStats.movelist, "%s (only move)", buf1);
9412                 programStats.depth = 1;
9413                 programStats.nr_moves = 1;
9414                 programStats.moves_left = 1;
9415                 programStats.nodes = 1;
9416                 programStats.time = 1;
9417                 programStats.got_only_move = 1;
9418
9419                 /* Not really, but we also use this member to
9420                    mean "line isn't going to change" (Crafty
9421                    isn't searching, so stats won't change) */
9422                 programStats.line_is_book = 1;
9423
9424                 SendProgramStatsToFrontend( cps, &programStats );
9425
9426                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9427                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9428                     DisplayMove(currentMove - 1);
9429                 }
9430                 return;
9431             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9432                               &time, &nodes, &plylev, &mvleft,
9433                               &mvtot, mvname) >= 5) {
9434                 /* The stat01: line is from Crafty (9.29+) in response
9435                    to the "." command */
9436                 programStats.seen_stat = 1;
9437                 cps->maybeThinking = TRUE;
9438
9439                 if (programStats.got_only_move || !appData.periodicUpdates)
9440                   return;
9441
9442                 programStats.depth = plylev;
9443                 programStats.time = time;
9444                 programStats.nodes = nodes;
9445                 programStats.moves_left = mvleft;
9446                 programStats.nr_moves = mvtot;
9447                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9448                 programStats.ok_to_send = 1;
9449                 programStats.movelist[0] = '\0';
9450
9451                 SendProgramStatsToFrontend( cps, &programStats );
9452
9453                 return;
9454
9455             } else if (strncmp(message,"++",2) == 0) {
9456                 /* Crafty 9.29+ outputs this */
9457                 programStats.got_fail = 2;
9458                 return;
9459
9460             } else if (strncmp(message,"--",2) == 0) {
9461                 /* Crafty 9.29+ outputs this */
9462                 programStats.got_fail = 1;
9463                 return;
9464
9465             } else if (thinkOutput[0] != NULLCHAR &&
9466                        strncmp(message, "    ", 4) == 0) {
9467                 unsigned message_len;
9468
9469                 p = message;
9470                 while (*p && *p == ' ') p++;
9471
9472                 message_len = strlen( p );
9473
9474                 /* [AS] Avoid buffer overflow */
9475                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9476                     strcat(thinkOutput, " ");
9477                     strcat(thinkOutput, p);
9478                 }
9479
9480                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9481                     strcat(programStats.movelist, " ");
9482                     strcat(programStats.movelist, p);
9483                 }
9484
9485                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9486                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9487                     DisplayMove(currentMove - 1);
9488                 }
9489                 return;
9490             }
9491         }
9492         else {
9493             buf1[0] = NULLCHAR;
9494
9495             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9496                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9497             {
9498                 ChessProgramStats cpstats;
9499
9500                 if (plyext != ' ' && plyext != '\t') {
9501                     time *= 100;
9502                 }
9503
9504                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9505                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9506                     curscore = -curscore;
9507                 }
9508
9509                 cpstats.depth = plylev;
9510                 cpstats.nodes = nodes;
9511                 cpstats.time = time;
9512                 cpstats.score = curscore;
9513                 cpstats.got_only_move = 0;
9514                 cpstats.movelist[0] = '\0';
9515
9516                 if (buf1[0] != NULLCHAR) {
9517                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9518                 }
9519
9520                 cpstats.ok_to_send = 0;
9521                 cpstats.line_is_book = 0;
9522                 cpstats.nr_moves = 0;
9523                 cpstats.moves_left = 0;
9524
9525                 SendProgramStatsToFrontend( cps, &cpstats );
9526             }
9527         }
9528     }
9529 }
9530
9531
9532 /* Parse a game score from the character string "game", and
9533    record it as the history of the current game.  The game
9534    score is NOT assumed to start from the standard position.
9535    The display is not updated in any way.
9536    */
9537 void
9538 ParseGameHistory (char *game)
9539 {
9540     ChessMove moveType;
9541     int fromX, fromY, toX, toY, boardIndex;
9542     char promoChar;
9543     char *p, *q;
9544     char buf[MSG_SIZ];
9545
9546     if (appData.debugMode)
9547       fprintf(debugFP, "Parsing game history: %s\n", game);
9548
9549     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9550     gameInfo.site = StrSave(appData.icsHost);
9551     gameInfo.date = PGNDate();
9552     gameInfo.round = StrSave("-");
9553
9554     /* Parse out names of players */
9555     while (*game == ' ') game++;
9556     p = buf;
9557     while (*game != ' ') *p++ = *game++;
9558     *p = NULLCHAR;
9559     gameInfo.white = StrSave(buf);
9560     while (*game == ' ') game++;
9561     p = buf;
9562     while (*game != ' ' && *game != '\n') *p++ = *game++;
9563     *p = NULLCHAR;
9564     gameInfo.black = StrSave(buf);
9565
9566     /* Parse moves */
9567     boardIndex = blackPlaysFirst ? 1 : 0;
9568     yynewstr(game);
9569     for (;;) {
9570         yyboardindex = boardIndex;
9571         moveType = (ChessMove) Myylex();
9572         switch (moveType) {
9573           case IllegalMove:             /* maybe suicide chess, etc. */
9574   if (appData.debugMode) {
9575     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9576     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9577     setbuf(debugFP, NULL);
9578   }
9579           case WhitePromotion:
9580           case BlackPromotion:
9581           case WhiteNonPromotion:
9582           case BlackNonPromotion:
9583           case NormalMove:
9584           case FirstLeg:
9585           case WhiteCapturesEnPassant:
9586           case BlackCapturesEnPassant:
9587           case WhiteKingSideCastle:
9588           case WhiteQueenSideCastle:
9589           case BlackKingSideCastle:
9590           case BlackQueenSideCastle:
9591           case WhiteKingSideCastleWild:
9592           case WhiteQueenSideCastleWild:
9593           case BlackKingSideCastleWild:
9594           case BlackQueenSideCastleWild:
9595           /* PUSH Fabien */
9596           case WhiteHSideCastleFR:
9597           case WhiteASideCastleFR:
9598           case BlackHSideCastleFR:
9599           case BlackASideCastleFR:
9600           /* POP Fabien */
9601             fromX = currentMoveString[0] - AAA;
9602             fromY = currentMoveString[1] - ONE;
9603             toX = currentMoveString[2] - AAA;
9604             toY = currentMoveString[3] - ONE;
9605             promoChar = currentMoveString[4];
9606             break;
9607           case WhiteDrop:
9608           case BlackDrop:
9609             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9610             fromX = moveType == WhiteDrop ?
9611               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9612             (int) CharToPiece(ToLower(currentMoveString[0]));
9613             fromY = DROP_RANK;
9614             toX = currentMoveString[2] - AAA;
9615             toY = currentMoveString[3] - ONE;
9616             promoChar = NULLCHAR;
9617             break;
9618           case AmbiguousMove:
9619             /* bug? */
9620             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9621   if (appData.debugMode) {
9622     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9623     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9624     setbuf(debugFP, NULL);
9625   }
9626             DisplayError(buf, 0);
9627             return;
9628           case ImpossibleMove:
9629             /* bug? */
9630             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9631   if (appData.debugMode) {
9632     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9633     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9634     setbuf(debugFP, NULL);
9635   }
9636             DisplayError(buf, 0);
9637             return;
9638           case EndOfFile:
9639             if (boardIndex < backwardMostMove) {
9640                 /* Oops, gap.  How did that happen? */
9641                 DisplayError(_("Gap in move list"), 0);
9642                 return;
9643             }
9644             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9645             if (boardIndex > forwardMostMove) {
9646                 forwardMostMove = boardIndex;
9647             }
9648             return;
9649           case ElapsedTime:
9650             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9651                 strcat(parseList[boardIndex-1], " ");
9652                 strcat(parseList[boardIndex-1], yy_text);
9653             }
9654             continue;
9655           case Comment:
9656           case PGNTag:
9657           case NAG:
9658           default:
9659             /* ignore */
9660             continue;
9661           case WhiteWins:
9662           case BlackWins:
9663           case GameIsDrawn:
9664           case GameUnfinished:
9665             if (gameMode == IcsExamining) {
9666                 if (boardIndex < backwardMostMove) {
9667                     /* Oops, gap.  How did that happen? */
9668                     return;
9669                 }
9670                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9671                 return;
9672             }
9673             gameInfo.result = moveType;
9674             p = strchr(yy_text, '{');
9675             if (p == NULL) p = strchr(yy_text, '(');
9676             if (p == NULL) {
9677                 p = yy_text;
9678                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9679             } else {
9680                 q = strchr(p, *p == '{' ? '}' : ')');
9681                 if (q != NULL) *q = NULLCHAR;
9682                 p++;
9683             }
9684             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9685             gameInfo.resultDetails = StrSave(p);
9686             continue;
9687         }
9688         if (boardIndex >= forwardMostMove &&
9689             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9690             backwardMostMove = blackPlaysFirst ? 1 : 0;
9691             return;
9692         }
9693         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9694                                  fromY, fromX, toY, toX, promoChar,
9695                                  parseList[boardIndex]);
9696         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9697         /* currentMoveString is set as a side-effect of yylex */
9698         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9699         strcat(moveList[boardIndex], "\n");
9700         boardIndex++;
9701         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9702         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9703           case MT_NONE:
9704           case MT_STALEMATE:
9705           default:
9706             break;
9707           case MT_CHECK:
9708             if(gameInfo.variant != VariantShogi)
9709                 strcat(parseList[boardIndex - 1], "+");
9710             break;
9711           case MT_CHECKMATE:
9712           case MT_STAINMATE:
9713             strcat(parseList[boardIndex - 1], "#");
9714             break;
9715         }
9716     }
9717 }
9718
9719
9720 /* Apply a move to the given board  */
9721 void
9722 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9723 {
9724   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9725   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9726
9727     /* [HGM] compute & store e.p. status and castling rights for new position */
9728     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9729
9730       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9731       oldEP = (signed char)board[EP_STATUS];
9732       board[EP_STATUS] = EP_NONE;
9733
9734   if (fromY == DROP_RANK) {
9735         /* must be first */
9736         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9737             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9738             return;
9739         }
9740         piece = board[toY][toX] = (ChessSquare) fromX;
9741   } else {
9742       ChessSquare victim;
9743       int i;
9744
9745       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9746            victim = board[killY][killX],
9747            board[killY][killX] = EmptySquare,
9748            board[EP_STATUS] = EP_CAPTURE;
9749
9750       if( board[toY][toX] != EmptySquare ) {
9751            board[EP_STATUS] = EP_CAPTURE;
9752            if( (fromX != toX || fromY != toY) && // not igui!
9753                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9754                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9755                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9756            }
9757       }
9758
9759       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9760            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9761                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9762       } else
9763       if( board[fromY][fromX] == WhitePawn ) {
9764            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9765                board[EP_STATUS] = EP_PAWN_MOVE;
9766            if( toY-fromY==2) {
9767                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9768                         gameInfo.variant != VariantBerolina || toX < fromX)
9769                       board[EP_STATUS] = toX | berolina;
9770                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9771                         gameInfo.variant != VariantBerolina || toX > fromX)
9772                       board[EP_STATUS] = toX;
9773            }
9774       } else
9775       if( board[fromY][fromX] == BlackPawn ) {
9776            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9777                board[EP_STATUS] = EP_PAWN_MOVE;
9778            if( toY-fromY== -2) {
9779                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9780                         gameInfo.variant != VariantBerolina || toX < fromX)
9781                       board[EP_STATUS] = toX | berolina;
9782                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9783                         gameInfo.variant != VariantBerolina || toX > fromX)
9784                       board[EP_STATUS] = toX;
9785            }
9786        }
9787
9788        for(i=0; i<nrCastlingRights; i++) {
9789            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9790               board[CASTLING][i] == toX   && castlingRank[i] == toY
9791              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9792        }
9793
9794        if(gameInfo.variant == VariantSChess) { // update virginity
9795            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9796            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9797            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9798            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9799        }
9800
9801      if (fromX == toX && fromY == toY) return;
9802
9803      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9804      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9805      if(gameInfo.variant == VariantKnightmate)
9806          king += (int) WhiteUnicorn - (int) WhiteKing;
9807
9808     /* Code added by Tord: */
9809     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9810     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9811         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9812       board[fromY][fromX] = EmptySquare;
9813       board[toY][toX] = EmptySquare;
9814       if((toX > fromX) != (piece == WhiteRook)) {
9815         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9816       } else {
9817         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9818       }
9819     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9820                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9821       board[fromY][fromX] = EmptySquare;
9822       board[toY][toX] = EmptySquare;
9823       if((toX > fromX) != (piece == BlackRook)) {
9824         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9825       } else {
9826         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9827       }
9828     /* End of code added by Tord */
9829
9830     } else if (board[fromY][fromX] == king
9831         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9832         && toY == fromY && toX > fromX+1) {
9833         board[fromY][fromX] = EmptySquare;
9834         board[toY][toX] = king;
9835         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9836         board[fromY][BOARD_RGHT-1] = EmptySquare;
9837     } else if (board[fromY][fromX] == king
9838         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9839                && toY == fromY && toX < fromX-1) {
9840         board[fromY][fromX] = EmptySquare;
9841         board[toY][toX] = king;
9842         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9843         board[fromY][BOARD_LEFT] = EmptySquare;
9844     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9845                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9846                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9847                ) {
9848         /* white pawn promotion */
9849         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9850         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9851             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9852         board[fromY][fromX] = EmptySquare;
9853     } else if ((fromY >= BOARD_HEIGHT>>1)
9854                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9855                && (toX != fromX)
9856                && gameInfo.variant != VariantXiangqi
9857                && gameInfo.variant != VariantBerolina
9858                && (board[fromY][fromX] == WhitePawn)
9859                && (board[toY][toX] == EmptySquare)) {
9860         board[fromY][fromX] = EmptySquare;
9861         board[toY][toX] = WhitePawn;
9862         captured = board[toY - 1][toX];
9863         board[toY - 1][toX] = EmptySquare;
9864     } else if ((fromY == BOARD_HEIGHT-4)
9865                && (toX == fromX)
9866                && gameInfo.variant == VariantBerolina
9867                && (board[fromY][fromX] == WhitePawn)
9868                && (board[toY][toX] == EmptySquare)) {
9869         board[fromY][fromX] = EmptySquare;
9870         board[toY][toX] = WhitePawn;
9871         if(oldEP & EP_BEROLIN_A) {
9872                 captured = board[fromY][fromX-1];
9873                 board[fromY][fromX-1] = EmptySquare;
9874         }else{  captured = board[fromY][fromX+1];
9875                 board[fromY][fromX+1] = EmptySquare;
9876         }
9877     } else if (board[fromY][fromX] == king
9878         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9879                && toY == fromY && toX > fromX+1) {
9880         board[fromY][fromX] = EmptySquare;
9881         board[toY][toX] = king;
9882         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9883         board[fromY][BOARD_RGHT-1] = EmptySquare;
9884     } else if (board[fromY][fromX] == king
9885         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9886                && toY == fromY && toX < fromX-1) {
9887         board[fromY][fromX] = EmptySquare;
9888         board[toY][toX] = king;
9889         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9890         board[fromY][BOARD_LEFT] = EmptySquare;
9891     } else if (fromY == 7 && fromX == 3
9892                && board[fromY][fromX] == BlackKing
9893                && toY == 7 && toX == 5) {
9894         board[fromY][fromX] = EmptySquare;
9895         board[toY][toX] = BlackKing;
9896         board[fromY][7] = EmptySquare;
9897         board[toY][4] = BlackRook;
9898     } else if (fromY == 7 && fromX == 3
9899                && board[fromY][fromX] == BlackKing
9900                && toY == 7 && toX == 1) {
9901         board[fromY][fromX] = EmptySquare;
9902         board[toY][toX] = BlackKing;
9903         board[fromY][0] = EmptySquare;
9904         board[toY][2] = BlackRook;
9905     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9906                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9907                && toY < promoRank && promoChar
9908                ) {
9909         /* black pawn promotion */
9910         board[toY][toX] = CharToPiece(ToLower(promoChar));
9911         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9912             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9913         board[fromY][fromX] = EmptySquare;
9914     } else if ((fromY < BOARD_HEIGHT>>1)
9915                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9916                && (toX != fromX)
9917                && gameInfo.variant != VariantXiangqi
9918                && gameInfo.variant != VariantBerolina
9919                && (board[fromY][fromX] == BlackPawn)
9920                && (board[toY][toX] == EmptySquare)) {
9921         board[fromY][fromX] = EmptySquare;
9922         board[toY][toX] = BlackPawn;
9923         captured = board[toY + 1][toX];
9924         board[toY + 1][toX] = EmptySquare;
9925     } else if ((fromY == 3)
9926                && (toX == fromX)
9927                && gameInfo.variant == VariantBerolina
9928                && (board[fromY][fromX] == BlackPawn)
9929                && (board[toY][toX] == EmptySquare)) {
9930         board[fromY][fromX] = EmptySquare;
9931         board[toY][toX] = BlackPawn;
9932         if(oldEP & EP_BEROLIN_A) {
9933                 captured = board[fromY][fromX-1];
9934                 board[fromY][fromX-1] = EmptySquare;
9935         }else{  captured = board[fromY][fromX+1];
9936                 board[fromY][fromX+1] = EmptySquare;
9937         }
9938     } else {
9939         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9940         board[fromY][fromX] = EmptySquare;
9941         board[toY][toX] = piece;
9942     }
9943   }
9944
9945     if (gameInfo.holdingsWidth != 0) {
9946
9947       /* !!A lot more code needs to be written to support holdings  */
9948       /* [HGM] OK, so I have written it. Holdings are stored in the */
9949       /* penultimate board files, so they are automaticlly stored   */
9950       /* in the game history.                                       */
9951       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9952                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9953         /* Delete from holdings, by decreasing count */
9954         /* and erasing image if necessary            */
9955         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9956         if(p < (int) BlackPawn) { /* white drop */
9957              p -= (int)WhitePawn;
9958                  p = PieceToNumber((ChessSquare)p);
9959              if(p >= gameInfo.holdingsSize) p = 0;
9960              if(--board[p][BOARD_WIDTH-2] <= 0)
9961                   board[p][BOARD_WIDTH-1] = EmptySquare;
9962              if((int)board[p][BOARD_WIDTH-2] < 0)
9963                         board[p][BOARD_WIDTH-2] = 0;
9964         } else {                  /* black drop */
9965              p -= (int)BlackPawn;
9966                  p = PieceToNumber((ChessSquare)p);
9967              if(p >= gameInfo.holdingsSize) p = 0;
9968              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9969                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9970              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9971                         board[BOARD_HEIGHT-1-p][1] = 0;
9972         }
9973       }
9974       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9975           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9976         /* [HGM] holdings: Add to holdings, if holdings exist */
9977         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9978                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9979                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9980         }
9981         p = (int) captured;
9982         if (p >= (int) BlackPawn) {
9983           p -= (int)BlackPawn;
9984           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9985                   /* in Shogi restore piece to its original  first */
9986                   captured = (ChessSquare) (DEMOTED captured);
9987                   p = DEMOTED p;
9988           }
9989           p = PieceToNumber((ChessSquare)p);
9990           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9991           board[p][BOARD_WIDTH-2]++;
9992           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9993         } else {
9994           p -= (int)WhitePawn;
9995           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9996                   captured = (ChessSquare) (DEMOTED captured);
9997                   p = DEMOTED p;
9998           }
9999           p = PieceToNumber((ChessSquare)p);
10000           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10001           board[BOARD_HEIGHT-1-p][1]++;
10002           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10003         }
10004       }
10005     } else if (gameInfo.variant == VariantAtomic) {
10006       if (captured != EmptySquare) {
10007         int y, x;
10008         for (y = toY-1; y <= toY+1; y++) {
10009           for (x = toX-1; x <= toX+1; x++) {
10010             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10011                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10012               board[y][x] = EmptySquare;
10013             }
10014           }
10015         }
10016         board[toY][toX] = EmptySquare;
10017       }
10018     }
10019
10020     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10021         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10022     } else
10023     if(promoChar == '+') {
10024         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10025         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10026         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10027           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10028     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10029         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10030         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10031            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10032         board[toY][toX] = newPiece;
10033     }
10034     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10035                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10036         // [HGM] superchess: take promotion piece out of holdings
10037         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10038         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10039             if(!--board[k][BOARD_WIDTH-2])
10040                 board[k][BOARD_WIDTH-1] = EmptySquare;
10041         } else {
10042             if(!--board[BOARD_HEIGHT-1-k][1])
10043                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10044         }
10045     }
10046 }
10047
10048 /* Updates forwardMostMove */
10049 void
10050 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10051 {
10052     int x = toX, y = toY;
10053     char *s = parseList[forwardMostMove];
10054     ChessSquare p = boards[forwardMostMove][toY][toX];
10055 //    forwardMostMove++; // [HGM] bare: moved downstream
10056
10057     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10058     (void) CoordsToAlgebraic(boards[forwardMostMove],
10059                              PosFlags(forwardMostMove),
10060                              fromY, fromX, y, x, promoChar,
10061                              s);
10062     if(killX >= 0 && killY >= 0)
10063         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10064
10065     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10066         int timeLeft; static int lastLoadFlag=0; int king, piece;
10067         piece = boards[forwardMostMove][fromY][fromX];
10068         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10069         if(gameInfo.variant == VariantKnightmate)
10070             king += (int) WhiteUnicorn - (int) WhiteKing;
10071         if(forwardMostMove == 0) {
10072             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10073                 fprintf(serverMoves, "%s;", UserName());
10074             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10075                 fprintf(serverMoves, "%s;", second.tidy);
10076             fprintf(serverMoves, "%s;", first.tidy);
10077             if(gameMode == MachinePlaysWhite)
10078                 fprintf(serverMoves, "%s;", UserName());
10079             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10080                 fprintf(serverMoves, "%s;", second.tidy);
10081         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10082         lastLoadFlag = loadFlag;
10083         // print base move
10084         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10085         // print castling suffix
10086         if( toY == fromY && piece == king ) {
10087             if(toX-fromX > 1)
10088                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10089             if(fromX-toX >1)
10090                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10091         }
10092         // e.p. suffix
10093         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10094              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10095              boards[forwardMostMove][toY][toX] == EmptySquare
10096              && fromX != toX && fromY != toY)
10097                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10098         // promotion suffix
10099         if(promoChar != NULLCHAR) {
10100             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10101                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10102                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10103             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10104         }
10105         if(!loadFlag) {
10106                 char buf[MOVE_LEN*2], *p; int len;
10107             fprintf(serverMoves, "/%d/%d",
10108                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10109             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10110             else                      timeLeft = blackTimeRemaining/1000;
10111             fprintf(serverMoves, "/%d", timeLeft);
10112                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10113                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10114                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10115                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10116             fprintf(serverMoves, "/%s", buf);
10117         }
10118         fflush(serverMoves);
10119     }
10120
10121     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10122         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10123       return;
10124     }
10125     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10126     if (commentList[forwardMostMove+1] != NULL) {
10127         free(commentList[forwardMostMove+1]);
10128         commentList[forwardMostMove+1] = NULL;
10129     }
10130     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10131     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10132     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10133     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10134     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10135     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10136     adjustedClock = FALSE;
10137     gameInfo.result = GameUnfinished;
10138     if (gameInfo.resultDetails != NULL) {
10139         free(gameInfo.resultDetails);
10140         gameInfo.resultDetails = NULL;
10141     }
10142     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10143                               moveList[forwardMostMove - 1]);
10144     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10145       case MT_NONE:
10146       case MT_STALEMATE:
10147       default:
10148         break;
10149       case MT_CHECK:
10150         if(gameInfo.variant != VariantShogi)
10151             strcat(parseList[forwardMostMove - 1], "+");
10152         break;
10153       case MT_CHECKMATE:
10154       case MT_STAINMATE:
10155         strcat(parseList[forwardMostMove - 1], "#");
10156         break;
10157     }
10158 }
10159
10160 /* Updates currentMove if not pausing */
10161 void
10162 ShowMove (int fromX, int fromY, int toX, int toY)
10163 {
10164     int instant = (gameMode == PlayFromGameFile) ?
10165         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10166     if(appData.noGUI) return;
10167     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10168         if (!instant) {
10169             if (forwardMostMove == currentMove + 1) {
10170                 AnimateMove(boards[forwardMostMove - 1],
10171                             fromX, fromY, toX, toY);
10172             }
10173         }
10174         currentMove = forwardMostMove;
10175     }
10176
10177     killX = killY = -1; // [HGM] lion: used up
10178
10179     if (instant) return;
10180
10181     DisplayMove(currentMove - 1);
10182     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10183             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10184                 SetHighlights(fromX, fromY, toX, toY);
10185             }
10186     }
10187     DrawPosition(FALSE, boards[currentMove]);
10188     DisplayBothClocks();
10189     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10190 }
10191
10192 void
10193 SendEgtPath (ChessProgramState *cps)
10194 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10195         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10196
10197         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10198
10199         while(*p) {
10200             char c, *q = name+1, *r, *s;
10201
10202             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10203             while(*p && *p != ',') *q++ = *p++;
10204             *q++ = ':'; *q = 0;
10205             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10206                 strcmp(name, ",nalimov:") == 0 ) {
10207                 // take nalimov path from the menu-changeable option first, if it is defined
10208               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10209                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10210             } else
10211             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10212                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10213                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10214                 s = r = StrStr(s, ":") + 1; // beginning of path info
10215                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10216                 c = *r; *r = 0;             // temporarily null-terminate path info
10217                     *--q = 0;               // strip of trailig ':' from name
10218                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10219                 *r = c;
10220                 SendToProgram(buf,cps);     // send egtbpath command for this format
10221             }
10222             if(*p == ',') p++; // read away comma to position for next format name
10223         }
10224 }
10225
10226 static int
10227 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10228 {
10229       int width = 8, height = 8, holdings = 0;             // most common sizes
10230       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10231       // correct the deviations default for each variant
10232       if( v == VariantXiangqi ) width = 9,  height = 10;
10233       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10234       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10235       if( v == VariantCapablanca || v == VariantCapaRandom ||
10236           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10237                                 width = 10;
10238       if( v == VariantCourier ) width = 12;
10239       if( v == VariantSuper )                            holdings = 8;
10240       if( v == VariantGreat )   width = 10,              holdings = 8;
10241       if( v == VariantSChess )                           holdings = 7;
10242       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10243       if( v == VariantChuChess) width = 10, height = 10;
10244       if( v == VariantChu )     width = 12, height = 12;
10245       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10246              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10247              holdingsSize >= 0 && holdingsSize != holdings;
10248 }
10249
10250 char variantError[MSG_SIZ];
10251
10252 char *
10253 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10254 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10255       char *p, *variant = VariantName(v);
10256       static char b[MSG_SIZ];
10257       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10258            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10259                                                holdingsSize, variant); // cook up sized variant name
10260            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10261            if(StrStr(list, b) == NULL) {
10262                // specific sized variant not known, check if general sizing allowed
10263                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10264                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10265                             boardWidth, boardHeight, holdingsSize, engine);
10266                    return NULL;
10267                }
10268                /* [HGM] here we really should compare with the maximum supported board size */
10269            }
10270       } else snprintf(b, MSG_SIZ,"%s", variant);
10271       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10272       p = StrStr(list, b);
10273       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10274       if(p == NULL) {
10275           // occurs not at all in list, or only as sub-string
10276           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10277           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10278               int l = strlen(variantError);
10279               char *q;
10280               while(p != list && p[-1] != ',') p--;
10281               q = strchr(p, ',');
10282               if(q) *q = NULLCHAR;
10283               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10284               if(q) *q= ',';
10285           }
10286           return NULL;
10287       }
10288       return b;
10289 }
10290
10291 void
10292 InitChessProgram (ChessProgramState *cps, int setup)
10293 /* setup needed to setup FRC opening position */
10294 {
10295     char buf[MSG_SIZ], *b;
10296     if (appData.noChessProgram) return;
10297     hintRequested = FALSE;
10298     bookRequested = FALSE;
10299
10300     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10301     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10302     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10303     if(cps->memSize) { /* [HGM] memory */
10304       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10305         SendToProgram(buf, cps);
10306     }
10307     SendEgtPath(cps); /* [HGM] EGT */
10308     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10309       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10310         SendToProgram(buf, cps);
10311     }
10312
10313     SendToProgram(cps->initString, cps);
10314     if (gameInfo.variant != VariantNormal &&
10315         gameInfo.variant != VariantLoadable
10316         /* [HGM] also send variant if board size non-standard */
10317         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10318
10319       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10320                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10321       if (b == NULL) {
10322         DisplayFatalError(variantError, 0, 1);
10323         return;
10324       }
10325
10326       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10327       SendToProgram(buf, cps);
10328     }
10329     currentlyInitializedVariant = gameInfo.variant;
10330
10331     /* [HGM] send opening position in FRC to first engine */
10332     if(setup) {
10333           SendToProgram("force\n", cps);
10334           SendBoard(cps, 0);
10335           /* engine is now in force mode! Set flag to wake it up after first move. */
10336           setboardSpoiledMachineBlack = 1;
10337     }
10338
10339     if (cps->sendICS) {
10340       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10341       SendToProgram(buf, cps);
10342     }
10343     cps->maybeThinking = FALSE;
10344     cps->offeredDraw = 0;
10345     if (!appData.icsActive) {
10346         SendTimeControl(cps, movesPerSession, timeControl,
10347                         timeIncrement, appData.searchDepth,
10348                         searchTime);
10349     }
10350     if (appData.showThinking
10351         // [HGM] thinking: four options require thinking output to be sent
10352         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10353                                 ) {
10354         SendToProgram("post\n", cps);
10355     }
10356     SendToProgram("hard\n", cps);
10357     if (!appData.ponderNextMove) {
10358         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10359            it without being sure what state we are in first.  "hard"
10360            is not a toggle, so that one is OK.
10361          */
10362         SendToProgram("easy\n", cps);
10363     }
10364     if (cps->usePing) {
10365       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10366       SendToProgram(buf, cps);
10367     }
10368     cps->initDone = TRUE;
10369     ClearEngineOutputPane(cps == &second);
10370 }
10371
10372
10373 void
10374 ResendOptions (ChessProgramState *cps)
10375 { // send the stored value of the options
10376   int i;
10377   char buf[MSG_SIZ];
10378   Option *opt = cps->option;
10379   for(i=0; i<cps->nrOptions; i++, opt++) {
10380       switch(opt->type) {
10381         case Spin:
10382         case Slider:
10383         case CheckBox:
10384             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10385           break;
10386         case ComboBox:
10387           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10388           break;
10389         default:
10390             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10391           break;
10392         case Button:
10393         case SaveButton:
10394           continue;
10395       }
10396       SendToProgram(buf, cps);
10397   }
10398 }
10399
10400 void
10401 StartChessProgram (ChessProgramState *cps)
10402 {
10403     char buf[MSG_SIZ];
10404     int err;
10405
10406     if (appData.noChessProgram) return;
10407     cps->initDone = FALSE;
10408
10409     if (strcmp(cps->host, "localhost") == 0) {
10410         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10411     } else if (*appData.remoteShell == NULLCHAR) {
10412         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10413     } else {
10414         if (*appData.remoteUser == NULLCHAR) {
10415           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10416                     cps->program);
10417         } else {
10418           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10419                     cps->host, appData.remoteUser, cps->program);
10420         }
10421         err = StartChildProcess(buf, "", &cps->pr);
10422     }
10423
10424     if (err != 0) {
10425       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10426         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10427         if(cps != &first) return;
10428         appData.noChessProgram = TRUE;
10429         ThawUI();
10430         SetNCPMode();
10431 //      DisplayFatalError(buf, err, 1);
10432 //      cps->pr = NoProc;
10433 //      cps->isr = NULL;
10434         return;
10435     }
10436
10437     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10438     if (cps->protocolVersion > 1) {
10439       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10440       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10441         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10442         cps->comboCnt = 0;  //                and values of combo boxes
10443       }
10444       SendToProgram(buf, cps);
10445       if(cps->reload) ResendOptions(cps);
10446     } else {
10447       SendToProgram("xboard\n", cps);
10448     }
10449 }
10450
10451 void
10452 TwoMachinesEventIfReady P((void))
10453 {
10454   static int curMess = 0;
10455   if (first.lastPing != first.lastPong) {
10456     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10457     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10458     return;
10459   }
10460   if (second.lastPing != second.lastPong) {
10461     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10462     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10463     return;
10464   }
10465   DisplayMessage("", ""); curMess = 0;
10466   TwoMachinesEvent();
10467 }
10468
10469 char *
10470 MakeName (char *template)
10471 {
10472     time_t clock;
10473     struct tm *tm;
10474     static char buf[MSG_SIZ];
10475     char *p = buf;
10476     int i;
10477
10478     clock = time((time_t *)NULL);
10479     tm = localtime(&clock);
10480
10481     while(*p++ = *template++) if(p[-1] == '%') {
10482         switch(*template++) {
10483           case 0:   *p = 0; return buf;
10484           case 'Y': i = tm->tm_year+1900; break;
10485           case 'y': i = tm->tm_year-100; break;
10486           case 'M': i = tm->tm_mon+1; break;
10487           case 'd': i = tm->tm_mday; break;
10488           case 'h': i = tm->tm_hour; break;
10489           case 'm': i = tm->tm_min; break;
10490           case 's': i = tm->tm_sec; break;
10491           default:  i = 0;
10492         }
10493         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10494     }
10495     return buf;
10496 }
10497
10498 int
10499 CountPlayers (char *p)
10500 {
10501     int n = 0;
10502     while(p = strchr(p, '\n')) p++, n++; // count participants
10503     return n;
10504 }
10505
10506 FILE *
10507 WriteTourneyFile (char *results, FILE *f)
10508 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10509     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10510     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10511         // create a file with tournament description
10512         fprintf(f, "-participants {%s}\n", appData.participants);
10513         fprintf(f, "-seedBase %d\n", appData.seedBase);
10514         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10515         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10516         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10517         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10518         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10519         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10520         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10521         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10522         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10523         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10524         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10525         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10526         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10527         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10528         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10529         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10530         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10531         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10532         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10533         fprintf(f, "-smpCores %d\n", appData.smpCores);
10534         if(searchTime > 0)
10535                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10536         else {
10537                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10538                 fprintf(f, "-tc %s\n", appData.timeControl);
10539                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10540         }
10541         fprintf(f, "-results \"%s\"\n", results);
10542     }
10543     return f;
10544 }
10545
10546 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10547
10548 void
10549 Substitute (char *participants, int expunge)
10550 {
10551     int i, changed, changes=0, nPlayers=0;
10552     char *p, *q, *r, buf[MSG_SIZ];
10553     if(participants == NULL) return;
10554     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10555     r = p = participants; q = appData.participants;
10556     while(*p && *p == *q) {
10557         if(*p == '\n') r = p+1, nPlayers++;
10558         p++; q++;
10559     }
10560     if(*p) { // difference
10561         while(*p && *p++ != '\n');
10562         while(*q && *q++ != '\n');
10563       changed = nPlayers;
10564         changes = 1 + (strcmp(p, q) != 0);
10565     }
10566     if(changes == 1) { // a single engine mnemonic was changed
10567         q = r; while(*q) nPlayers += (*q++ == '\n');
10568         p = buf; while(*r && (*p = *r++) != '\n') p++;
10569         *p = NULLCHAR;
10570         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10571         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10572         if(mnemonic[i]) { // The substitute is valid
10573             FILE *f;
10574             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10575                 flock(fileno(f), LOCK_EX);
10576                 ParseArgsFromFile(f);
10577                 fseek(f, 0, SEEK_SET);
10578                 FREE(appData.participants); appData.participants = participants;
10579                 if(expunge) { // erase results of replaced engine
10580                     int len = strlen(appData.results), w, b, dummy;
10581                     for(i=0; i<len; i++) {
10582                         Pairing(i, nPlayers, &w, &b, &dummy);
10583                         if((w == changed || b == changed) && appData.results[i] == '*') {
10584                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10585                             fclose(f);
10586                             return;
10587                         }
10588                     }
10589                     for(i=0; i<len; i++) {
10590                         Pairing(i, nPlayers, &w, &b, &dummy);
10591                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10592                     }
10593                 }
10594                 WriteTourneyFile(appData.results, f);
10595                 fclose(f); // release lock
10596                 return;
10597             }
10598         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10599     }
10600     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10601     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10602     free(participants);
10603     return;
10604 }
10605
10606 int
10607 CheckPlayers (char *participants)
10608 {
10609         int i;
10610         char buf[MSG_SIZ], *p;
10611         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10612         while(p = strchr(participants, '\n')) {
10613             *p = NULLCHAR;
10614             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10615             if(!mnemonic[i]) {
10616                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10617                 *p = '\n';
10618                 DisplayError(buf, 0);
10619                 return 1;
10620             }
10621             *p = '\n';
10622             participants = p + 1;
10623         }
10624         return 0;
10625 }
10626
10627 int
10628 CreateTourney (char *name)
10629 {
10630         FILE *f;
10631         if(matchMode && strcmp(name, appData.tourneyFile)) {
10632              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10633         }
10634         if(name[0] == NULLCHAR) {
10635             if(appData.participants[0])
10636                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10637             return 0;
10638         }
10639         f = fopen(name, "r");
10640         if(f) { // file exists
10641             ASSIGN(appData.tourneyFile, name);
10642             ParseArgsFromFile(f); // parse it
10643         } else {
10644             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10645             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10646                 DisplayError(_("Not enough participants"), 0);
10647                 return 0;
10648             }
10649             if(CheckPlayers(appData.participants)) return 0;
10650             ASSIGN(appData.tourneyFile, name);
10651             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10652             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10653         }
10654         fclose(f);
10655         appData.noChessProgram = FALSE;
10656         appData.clockMode = TRUE;
10657         SetGNUMode();
10658         return 1;
10659 }
10660
10661 int
10662 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10663 {
10664     char buf[MSG_SIZ], *p, *q;
10665     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10666     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10667     skip = !all && group[0]; // if group requested, we start in skip mode
10668     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10669         p = names; q = buf; header = 0;
10670         while(*p && *p != '\n') *q++ = *p++;
10671         *q = 0;
10672         if(*p == '\n') p++;
10673         if(buf[0] == '#') {
10674             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10675             depth++; // we must be entering a new group
10676             if(all) continue; // suppress printing group headers when complete list requested
10677             header = 1;
10678             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10679         }
10680         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10681         if(engineList[i]) free(engineList[i]);
10682         engineList[i] = strdup(buf);
10683         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10684         if(engineMnemonic[i]) free(engineMnemonic[i]);
10685         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10686             strcat(buf, " (");
10687             sscanf(q + 8, "%s", buf + strlen(buf));
10688             strcat(buf, ")");
10689         }
10690         engineMnemonic[i] = strdup(buf);
10691         i++;
10692     }
10693     engineList[i] = engineMnemonic[i] = NULL;
10694     return i;
10695 }
10696
10697 // following implemented as macro to avoid type limitations
10698 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10699
10700 void
10701 SwapEngines (int n)
10702 {   // swap settings for first engine and other engine (so far only some selected options)
10703     int h;
10704     char *p;
10705     if(n == 0) return;
10706     SWAP(directory, p)
10707     SWAP(chessProgram, p)
10708     SWAP(isUCI, h)
10709     SWAP(hasOwnBookUCI, h)
10710     SWAP(protocolVersion, h)
10711     SWAP(reuse, h)
10712     SWAP(scoreIsAbsolute, h)
10713     SWAP(timeOdds, h)
10714     SWAP(logo, p)
10715     SWAP(pgnName, p)
10716     SWAP(pvSAN, h)
10717     SWAP(engOptions, p)
10718     SWAP(engInitString, p)
10719     SWAP(computerString, p)
10720     SWAP(features, p)
10721     SWAP(fenOverride, p)
10722     SWAP(NPS, h)
10723     SWAP(accumulateTC, h)
10724     SWAP(host, p)
10725 }
10726
10727 int
10728 GetEngineLine (char *s, int n)
10729 {
10730     int i;
10731     char buf[MSG_SIZ];
10732     extern char *icsNames;
10733     if(!s || !*s) return 0;
10734     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10735     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10736     if(!mnemonic[i]) return 0;
10737     if(n == 11) return 1; // just testing if there was a match
10738     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10739     if(n == 1) SwapEngines(n);
10740     ParseArgsFromString(buf);
10741     if(n == 1) SwapEngines(n);
10742     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10743         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10744         ParseArgsFromString(buf);
10745     }
10746     return 1;
10747 }
10748
10749 int
10750 SetPlayer (int player, char *p)
10751 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10752     int i;
10753     char buf[MSG_SIZ], *engineName;
10754     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10755     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10756     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10757     if(mnemonic[i]) {
10758         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10759         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10760         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10761         ParseArgsFromString(buf);
10762     } else { // no engine with this nickname is installed!
10763         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10764         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10765         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10766         ModeHighlight();
10767         DisplayError(buf, 0);
10768         return 0;
10769     }
10770     free(engineName);
10771     return i;
10772 }
10773
10774 char *recentEngines;
10775
10776 void
10777 RecentEngineEvent (int nr)
10778 {
10779     int n;
10780 //    SwapEngines(1); // bump first to second
10781 //    ReplaceEngine(&second, 1); // and load it there
10782     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10783     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10784     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10785         ReplaceEngine(&first, 0);
10786         FloatToFront(&appData.recentEngineList, command[n]);
10787     }
10788 }
10789
10790 int
10791 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10792 {   // determine players from game number
10793     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10794
10795     if(appData.tourneyType == 0) {
10796         roundsPerCycle = (nPlayers - 1) | 1;
10797         pairingsPerRound = nPlayers / 2;
10798     } else if(appData.tourneyType > 0) {
10799         roundsPerCycle = nPlayers - appData.tourneyType;
10800         pairingsPerRound = appData.tourneyType;
10801     }
10802     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10803     gamesPerCycle = gamesPerRound * roundsPerCycle;
10804     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10805     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10806     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10807     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10808     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10809     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10810
10811     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10812     if(appData.roundSync) *syncInterval = gamesPerRound;
10813
10814     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10815
10816     if(appData.tourneyType == 0) {
10817         if(curPairing == (nPlayers-1)/2 ) {
10818             *whitePlayer = curRound;
10819             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10820         } else {
10821             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10822             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10823             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10824             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10825         }
10826     } else if(appData.tourneyType > 1) {
10827         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10828         *whitePlayer = curRound + appData.tourneyType;
10829     } else if(appData.tourneyType > 0) {
10830         *whitePlayer = curPairing;
10831         *blackPlayer = curRound + appData.tourneyType;
10832     }
10833
10834     // take care of white/black alternation per round.
10835     // For cycles and games this is already taken care of by default, derived from matchGame!
10836     return curRound & 1;
10837 }
10838
10839 int
10840 NextTourneyGame (int nr, int *swapColors)
10841 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10842     char *p, *q;
10843     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10844     FILE *tf;
10845     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10846     tf = fopen(appData.tourneyFile, "r");
10847     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10848     ParseArgsFromFile(tf); fclose(tf);
10849     InitTimeControls(); // TC might be altered from tourney file
10850
10851     nPlayers = CountPlayers(appData.participants); // count participants
10852     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10853     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10854
10855     if(syncInterval) {
10856         p = q = appData.results;
10857         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10858         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10859             DisplayMessage(_("Waiting for other game(s)"),"");
10860             waitingForGame = TRUE;
10861             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10862             return 0;
10863         }
10864         waitingForGame = FALSE;
10865     }
10866
10867     if(appData.tourneyType < 0) {
10868         if(nr>=0 && !pairingReceived) {
10869             char buf[1<<16];
10870             if(pairing.pr == NoProc) {
10871                 if(!appData.pairingEngine[0]) {
10872                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10873                     return 0;
10874                 }
10875                 StartChessProgram(&pairing); // starts the pairing engine
10876             }
10877             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10878             SendToProgram(buf, &pairing);
10879             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10880             SendToProgram(buf, &pairing);
10881             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10882         }
10883         pairingReceived = 0;                              // ... so we continue here
10884         *swapColors = 0;
10885         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10886         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10887         matchGame = 1; roundNr = nr / syncInterval + 1;
10888     }
10889
10890     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10891
10892     // redefine engines, engine dir, etc.
10893     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10894     if(first.pr == NoProc) {
10895       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10896       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10897     }
10898     if(second.pr == NoProc) {
10899       SwapEngines(1);
10900       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10901       SwapEngines(1);         // and make that valid for second engine by swapping
10902       InitEngine(&second, 1);
10903     }
10904     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10905     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10906     return OK;
10907 }
10908
10909 void
10910 NextMatchGame ()
10911 {   // performs game initialization that does not invoke engines, and then tries to start the game
10912     int res, firstWhite, swapColors = 0;
10913     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10914     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
10915         char buf[MSG_SIZ];
10916         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10917         if(strcmp(buf, currentDebugFile)) { // name has changed
10918             FILE *f = fopen(buf, "w");
10919             if(f) { // if opening the new file failed, just keep using the old one
10920                 ASSIGN(currentDebugFile, buf);
10921                 fclose(debugFP);
10922                 debugFP = f;
10923             }
10924             if(appData.serverFileName) {
10925                 if(serverFP) fclose(serverFP);
10926                 serverFP = fopen(appData.serverFileName, "w");
10927                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10928                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10929             }
10930         }
10931     }
10932     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10933     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10934     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10935     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10936     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10937     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10938     Reset(FALSE, first.pr != NoProc);
10939     res = LoadGameOrPosition(matchGame); // setup game
10940     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10941     if(!res) return; // abort when bad game/pos file
10942     TwoMachinesEvent();
10943 }
10944
10945 void
10946 UserAdjudicationEvent (int result)
10947 {
10948     ChessMove gameResult = GameIsDrawn;
10949
10950     if( result > 0 ) {
10951         gameResult = WhiteWins;
10952     }
10953     else if( result < 0 ) {
10954         gameResult = BlackWins;
10955     }
10956
10957     if( gameMode == TwoMachinesPlay ) {
10958         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10959     }
10960 }
10961
10962
10963 // [HGM] save: calculate checksum of game to make games easily identifiable
10964 int
10965 StringCheckSum (char *s)
10966 {
10967         int i = 0;
10968         if(s==NULL) return 0;
10969         while(*s) i = i*259 + *s++;
10970         return i;
10971 }
10972
10973 int
10974 GameCheckSum ()
10975 {
10976         int i, sum=0;
10977         for(i=backwardMostMove; i<forwardMostMove; i++) {
10978                 sum += pvInfoList[i].depth;
10979                 sum += StringCheckSum(parseList[i]);
10980                 sum += StringCheckSum(commentList[i]);
10981                 sum *= 261;
10982         }
10983         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10984         return sum + StringCheckSum(commentList[i]);
10985 } // end of save patch
10986
10987 void
10988 GameEnds (ChessMove result, char *resultDetails, int whosays)
10989 {
10990     GameMode nextGameMode;
10991     int isIcsGame;
10992     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10993
10994     if(endingGame) return; /* [HGM] crash: forbid recursion */
10995     endingGame = 1;
10996     if(twoBoards) { // [HGM] dual: switch back to one board
10997         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10998         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10999     }
11000     if (appData.debugMode) {
11001       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11002               result, resultDetails ? resultDetails : "(null)", whosays);
11003     }
11004
11005     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11006
11007     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11008
11009     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11010         /* If we are playing on ICS, the server decides when the
11011            game is over, but the engine can offer to draw, claim
11012            a draw, or resign.
11013          */
11014 #if ZIPPY
11015         if (appData.zippyPlay && first.initDone) {
11016             if (result == GameIsDrawn) {
11017                 /* In case draw still needs to be claimed */
11018                 SendToICS(ics_prefix);
11019                 SendToICS("draw\n");
11020             } else if (StrCaseStr(resultDetails, "resign")) {
11021                 SendToICS(ics_prefix);
11022                 SendToICS("resign\n");
11023             }
11024         }
11025 #endif
11026         endingGame = 0; /* [HGM] crash */
11027         return;
11028     }
11029
11030     /* If we're loading the game from a file, stop */
11031     if (whosays == GE_FILE) {
11032       (void) StopLoadGameTimer();
11033       gameFileFP = NULL;
11034     }
11035
11036     /* Cancel draw offers */
11037     first.offeredDraw = second.offeredDraw = 0;
11038
11039     /* If this is an ICS game, only ICS can really say it's done;
11040        if not, anyone can. */
11041     isIcsGame = (gameMode == IcsPlayingWhite ||
11042                  gameMode == IcsPlayingBlack ||
11043                  gameMode == IcsObserving    ||
11044                  gameMode == IcsExamining);
11045
11046     if (!isIcsGame || whosays == GE_ICS) {
11047         /* OK -- not an ICS game, or ICS said it was done */
11048         StopClocks();
11049         if (!isIcsGame && !appData.noChessProgram)
11050           SetUserThinkingEnables();
11051
11052         /* [HGM] if a machine claims the game end we verify this claim */
11053         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11054             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11055                 char claimer;
11056                 ChessMove trueResult = (ChessMove) -1;
11057
11058                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11059                                             first.twoMachinesColor[0] :
11060                                             second.twoMachinesColor[0] ;
11061
11062                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11063                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11064                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11065                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11066                 } else
11067                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11068                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11069                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11070                 } else
11071                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11072                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11073                 }
11074
11075                 // now verify win claims, but not in drop games, as we don't understand those yet
11076                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11077                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11078                     (result == WhiteWins && claimer == 'w' ||
11079                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11080                       if (appData.debugMode) {
11081                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11082                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11083                       }
11084                       if(result != trueResult) {
11085                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11086                               result = claimer == 'w' ? BlackWins : WhiteWins;
11087                               resultDetails = buf;
11088                       }
11089                 } else
11090                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11091                     && (forwardMostMove <= backwardMostMove ||
11092                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11093                         (claimer=='b')==(forwardMostMove&1))
11094                                                                                   ) {
11095                       /* [HGM] verify: draws that were not flagged are false claims */
11096                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11097                       result = claimer == 'w' ? BlackWins : WhiteWins;
11098                       resultDetails = buf;
11099                 }
11100                 /* (Claiming a loss is accepted no questions asked!) */
11101             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11102                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11103                 result = GameUnfinished;
11104                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11105             }
11106             /* [HGM] bare: don't allow bare King to win */
11107             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11108                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11109                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11110                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11111                && result != GameIsDrawn)
11112             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11113                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11114                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11115                         if(p >= 0 && p <= (int)WhiteKing) k++;
11116                 }
11117                 if (appData.debugMode) {
11118                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11119                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11120                 }
11121                 if(k <= 1) {
11122                         result = GameIsDrawn;
11123                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11124                         resultDetails = buf;
11125                 }
11126             }
11127         }
11128
11129
11130         if(serverMoves != NULL && !loadFlag) { char c = '=';
11131             if(result==WhiteWins) c = '+';
11132             if(result==BlackWins) c = '-';
11133             if(resultDetails != NULL)
11134                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11135         }
11136         if (resultDetails != NULL) {
11137             gameInfo.result = result;
11138             gameInfo.resultDetails = StrSave(resultDetails);
11139
11140             /* display last move only if game was not loaded from file */
11141             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11142                 DisplayMove(currentMove - 1);
11143
11144             if (forwardMostMove != 0) {
11145                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11146                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11147                                                                 ) {
11148                     if (*appData.saveGameFile != NULLCHAR) {
11149                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11150                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11151                         else
11152                         SaveGameToFile(appData.saveGameFile, TRUE);
11153                     } else if (appData.autoSaveGames) {
11154                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11155                     }
11156                     if (*appData.savePositionFile != NULLCHAR) {
11157                         SavePositionToFile(appData.savePositionFile);
11158                     }
11159                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11160                 }
11161             }
11162
11163             /* Tell program how game ended in case it is learning */
11164             /* [HGM] Moved this to after saving the PGN, just in case */
11165             /* engine died and we got here through time loss. In that */
11166             /* case we will get a fatal error writing the pipe, which */
11167             /* would otherwise lose us the PGN.                       */
11168             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11169             /* output during GameEnds should never be fatal anymore   */
11170             if (gameMode == MachinePlaysWhite ||
11171                 gameMode == MachinePlaysBlack ||
11172                 gameMode == TwoMachinesPlay ||
11173                 gameMode == IcsPlayingWhite ||
11174                 gameMode == IcsPlayingBlack ||
11175                 gameMode == BeginningOfGame) {
11176                 char buf[MSG_SIZ];
11177                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11178                         resultDetails);
11179                 if (first.pr != NoProc) {
11180                     SendToProgram(buf, &first);
11181                 }
11182                 if (second.pr != NoProc &&
11183                     gameMode == TwoMachinesPlay) {
11184                     SendToProgram(buf, &second);
11185                 }
11186             }
11187         }
11188
11189         if (appData.icsActive) {
11190             if (appData.quietPlay &&
11191                 (gameMode == IcsPlayingWhite ||
11192                  gameMode == IcsPlayingBlack)) {
11193                 SendToICS(ics_prefix);
11194                 SendToICS("set shout 1\n");
11195             }
11196             nextGameMode = IcsIdle;
11197             ics_user_moved = FALSE;
11198             /* clean up premove.  It's ugly when the game has ended and the
11199              * premove highlights are still on the board.
11200              */
11201             if (gotPremove) {
11202               gotPremove = FALSE;
11203               ClearPremoveHighlights();
11204               DrawPosition(FALSE, boards[currentMove]);
11205             }
11206             if (whosays == GE_ICS) {
11207                 switch (result) {
11208                 case WhiteWins:
11209                     if (gameMode == IcsPlayingWhite)
11210                         PlayIcsWinSound();
11211                     else if(gameMode == IcsPlayingBlack)
11212                         PlayIcsLossSound();
11213                     break;
11214                 case BlackWins:
11215                     if (gameMode == IcsPlayingBlack)
11216                         PlayIcsWinSound();
11217                     else if(gameMode == IcsPlayingWhite)
11218                         PlayIcsLossSound();
11219                     break;
11220                 case GameIsDrawn:
11221                     PlayIcsDrawSound();
11222                     break;
11223                 default:
11224                     PlayIcsUnfinishedSound();
11225                 }
11226             }
11227             if(appData.quitNext) { ExitEvent(0); return; }
11228         } else if (gameMode == EditGame ||
11229                    gameMode == PlayFromGameFile ||
11230                    gameMode == AnalyzeMode ||
11231                    gameMode == AnalyzeFile) {
11232             nextGameMode = gameMode;
11233         } else {
11234             nextGameMode = EndOfGame;
11235         }
11236         pausing = FALSE;
11237         ModeHighlight();
11238     } else {
11239         nextGameMode = gameMode;
11240     }
11241
11242     if (appData.noChessProgram) {
11243         gameMode = nextGameMode;
11244         ModeHighlight();
11245         endingGame = 0; /* [HGM] crash */
11246         return;
11247     }
11248
11249     if (first.reuse) {
11250         /* Put first chess program into idle state */
11251         if (first.pr != NoProc &&
11252             (gameMode == MachinePlaysWhite ||
11253              gameMode == MachinePlaysBlack ||
11254              gameMode == TwoMachinesPlay ||
11255              gameMode == IcsPlayingWhite ||
11256              gameMode == IcsPlayingBlack ||
11257              gameMode == BeginningOfGame)) {
11258             SendToProgram("force\n", &first);
11259             if (first.usePing) {
11260               char buf[MSG_SIZ];
11261               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11262               SendToProgram(buf, &first);
11263             }
11264         }
11265     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11266         /* Kill off first chess program */
11267         if (first.isr != NULL)
11268           RemoveInputSource(first.isr);
11269         first.isr = NULL;
11270
11271         if (first.pr != NoProc) {
11272             ExitAnalyzeMode();
11273             DoSleep( appData.delayBeforeQuit );
11274             SendToProgram("quit\n", &first);
11275             DoSleep( appData.delayAfterQuit );
11276             DestroyChildProcess(first.pr, first.useSigterm);
11277             first.reload = TRUE;
11278         }
11279         first.pr = NoProc;
11280     }
11281     if (second.reuse) {
11282         /* Put second chess program into idle state */
11283         if (second.pr != NoProc &&
11284             gameMode == TwoMachinesPlay) {
11285             SendToProgram("force\n", &second);
11286             if (second.usePing) {
11287               char buf[MSG_SIZ];
11288               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11289               SendToProgram(buf, &second);
11290             }
11291         }
11292     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11293         /* Kill off second chess program */
11294         if (second.isr != NULL)
11295           RemoveInputSource(second.isr);
11296         second.isr = NULL;
11297
11298         if (second.pr != NoProc) {
11299             DoSleep( appData.delayBeforeQuit );
11300             SendToProgram("quit\n", &second);
11301             DoSleep( appData.delayAfterQuit );
11302             DestroyChildProcess(second.pr, second.useSigterm);
11303             second.reload = TRUE;
11304         }
11305         second.pr = NoProc;
11306     }
11307
11308     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11309         char resChar = '=';
11310         switch (result) {
11311         case WhiteWins:
11312           resChar = '+';
11313           if (first.twoMachinesColor[0] == 'w') {
11314             first.matchWins++;
11315           } else {
11316             second.matchWins++;
11317           }
11318           break;
11319         case BlackWins:
11320           resChar = '-';
11321           if (first.twoMachinesColor[0] == 'b') {
11322             first.matchWins++;
11323           } else {
11324             second.matchWins++;
11325           }
11326           break;
11327         case GameUnfinished:
11328           resChar = ' ';
11329         default:
11330           break;
11331         }
11332
11333         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11334         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11335             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11336             ReserveGame(nextGame, resChar); // sets nextGame
11337             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11338             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11339         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11340
11341         if (nextGame <= appData.matchGames && !abortMatch) {
11342             gameMode = nextGameMode;
11343             matchGame = nextGame; // this will be overruled in tourney mode!
11344             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11345             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11346             endingGame = 0; /* [HGM] crash */
11347             return;
11348         } else {
11349             gameMode = nextGameMode;
11350             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11351                      first.tidy, second.tidy,
11352                      first.matchWins, second.matchWins,
11353                      appData.matchGames - (first.matchWins + second.matchWins));
11354             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11355             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11356             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11357             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11358                 first.twoMachinesColor = "black\n";
11359                 second.twoMachinesColor = "white\n";
11360             } else {
11361                 first.twoMachinesColor = "white\n";
11362                 second.twoMachinesColor = "black\n";
11363             }
11364         }
11365     }
11366     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11367         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11368       ExitAnalyzeMode();
11369     gameMode = nextGameMode;
11370     ModeHighlight();
11371     endingGame = 0;  /* [HGM] crash */
11372     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11373         if(matchMode == TRUE) { // match through command line: exit with or without popup
11374             if(ranking) {
11375                 ToNrEvent(forwardMostMove);
11376                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11377                 else ExitEvent(0);
11378             } else DisplayFatalError(buf, 0, 0);
11379         } else { // match through menu; just stop, with or without popup
11380             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11381             ModeHighlight();
11382             if(ranking){
11383                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11384             } else DisplayNote(buf);
11385       }
11386       if(ranking) free(ranking);
11387     }
11388 }
11389
11390 /* Assumes program was just initialized (initString sent).
11391    Leaves program in force mode. */
11392 void
11393 FeedMovesToProgram (ChessProgramState *cps, int upto)
11394 {
11395     int i;
11396
11397     if (appData.debugMode)
11398       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11399               startedFromSetupPosition ? "position and " : "",
11400               backwardMostMove, upto, cps->which);
11401     if(currentlyInitializedVariant != gameInfo.variant) {
11402       char buf[MSG_SIZ];
11403         // [HGM] variantswitch: make engine aware of new variant
11404         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11405                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11406                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11407         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11408         SendToProgram(buf, cps);
11409         currentlyInitializedVariant = gameInfo.variant;
11410     }
11411     SendToProgram("force\n", cps);
11412     if (startedFromSetupPosition) {
11413         SendBoard(cps, backwardMostMove);
11414     if (appData.debugMode) {
11415         fprintf(debugFP, "feedMoves\n");
11416     }
11417     }
11418     for (i = backwardMostMove; i < upto; i++) {
11419         SendMoveToProgram(i, cps);
11420     }
11421 }
11422
11423
11424 int
11425 ResurrectChessProgram ()
11426 {
11427      /* The chess program may have exited.
11428         If so, restart it and feed it all the moves made so far. */
11429     static int doInit = 0;
11430
11431     if (appData.noChessProgram) return 1;
11432
11433     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11434         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11435         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11436         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11437     } else {
11438         if (first.pr != NoProc) return 1;
11439         StartChessProgram(&first);
11440     }
11441     InitChessProgram(&first, FALSE);
11442     FeedMovesToProgram(&first, currentMove);
11443
11444     if (!first.sendTime) {
11445         /* can't tell gnuchess what its clock should read,
11446            so we bow to its notion. */
11447         ResetClocks();
11448         timeRemaining[0][currentMove] = whiteTimeRemaining;
11449         timeRemaining[1][currentMove] = blackTimeRemaining;
11450     }
11451
11452     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11453                 appData.icsEngineAnalyze) && first.analysisSupport) {
11454       SendToProgram("analyze\n", &first);
11455       first.analyzing = TRUE;
11456     }
11457     return 1;
11458 }
11459
11460 /*
11461  * Button procedures
11462  */
11463 void
11464 Reset (int redraw, int init)
11465 {
11466     int i;
11467
11468     if (appData.debugMode) {
11469         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11470                 redraw, init, gameMode);
11471     }
11472     CleanupTail(); // [HGM] vari: delete any stored variations
11473     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11474     pausing = pauseExamInvalid = FALSE;
11475     startedFromSetupPosition = blackPlaysFirst = FALSE;
11476     firstMove = TRUE;
11477     whiteFlag = blackFlag = FALSE;
11478     userOfferedDraw = FALSE;
11479     hintRequested = bookRequested = FALSE;
11480     first.maybeThinking = FALSE;
11481     second.maybeThinking = FALSE;
11482     first.bookSuspend = FALSE; // [HGM] book
11483     second.bookSuspend = FALSE;
11484     thinkOutput[0] = NULLCHAR;
11485     lastHint[0] = NULLCHAR;
11486     ClearGameInfo(&gameInfo);
11487     gameInfo.variant = StringToVariant(appData.variant);
11488     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11489     ics_user_moved = ics_clock_paused = FALSE;
11490     ics_getting_history = H_FALSE;
11491     ics_gamenum = -1;
11492     white_holding[0] = black_holding[0] = NULLCHAR;
11493     ClearProgramStats();
11494     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11495
11496     ResetFrontEnd();
11497     ClearHighlights();
11498     flipView = appData.flipView;
11499     ClearPremoveHighlights();
11500     gotPremove = FALSE;
11501     alarmSounded = FALSE;
11502     killX = killY = -1; // [HGM] lion
11503
11504     GameEnds(EndOfFile, NULL, GE_PLAYER);
11505     if(appData.serverMovesName != NULL) {
11506         /* [HGM] prepare to make moves file for broadcasting */
11507         clock_t t = clock();
11508         if(serverMoves != NULL) fclose(serverMoves);
11509         serverMoves = fopen(appData.serverMovesName, "r");
11510         if(serverMoves != NULL) {
11511             fclose(serverMoves);
11512             /* delay 15 sec before overwriting, so all clients can see end */
11513             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11514         }
11515         serverMoves = fopen(appData.serverMovesName, "w");
11516     }
11517
11518     ExitAnalyzeMode();
11519     gameMode = BeginningOfGame;
11520     ModeHighlight();
11521     if(appData.icsActive) gameInfo.variant = VariantNormal;
11522     currentMove = forwardMostMove = backwardMostMove = 0;
11523     MarkTargetSquares(1);
11524     InitPosition(redraw);
11525     for (i = 0; i < MAX_MOVES; i++) {
11526         if (commentList[i] != NULL) {
11527             free(commentList[i]);
11528             commentList[i] = NULL;
11529         }
11530     }
11531     ResetClocks();
11532     timeRemaining[0][0] = whiteTimeRemaining;
11533     timeRemaining[1][0] = blackTimeRemaining;
11534
11535     if (first.pr == NoProc) {
11536         StartChessProgram(&first);
11537     }
11538     if (init) {
11539             InitChessProgram(&first, startedFromSetupPosition);
11540     }
11541     DisplayTitle("");
11542     DisplayMessage("", "");
11543     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11544     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11545     ClearMap();        // [HGM] exclude: invalidate map
11546 }
11547
11548 void
11549 AutoPlayGameLoop ()
11550 {
11551     for (;;) {
11552         if (!AutoPlayOneMove())
11553           return;
11554         if (matchMode || appData.timeDelay == 0)
11555           continue;
11556         if (appData.timeDelay < 0)
11557           return;
11558         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11559         break;
11560     }
11561 }
11562
11563 void
11564 AnalyzeNextGame()
11565 {
11566     ReloadGame(1); // next game
11567 }
11568
11569 int
11570 AutoPlayOneMove ()
11571 {
11572     int fromX, fromY, toX, toY;
11573
11574     if (appData.debugMode) {
11575       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11576     }
11577
11578     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11579       return FALSE;
11580
11581     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11582       pvInfoList[currentMove].depth = programStats.depth;
11583       pvInfoList[currentMove].score = programStats.score;
11584       pvInfoList[currentMove].time  = 0;
11585       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11586       else { // append analysis of final position as comment
11587         char buf[MSG_SIZ];
11588         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11589         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11590       }
11591       programStats.depth = 0;
11592     }
11593
11594     if (currentMove >= forwardMostMove) {
11595       if(gameMode == AnalyzeFile) {
11596           if(appData.loadGameIndex == -1) {
11597             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11598           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11599           } else {
11600           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11601         }
11602       }
11603 //      gameMode = EndOfGame;
11604 //      ModeHighlight();
11605
11606       /* [AS] Clear current move marker at the end of a game */
11607       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11608
11609       return FALSE;
11610     }
11611
11612     toX = moveList[currentMove][2] - AAA;
11613     toY = moveList[currentMove][3] - ONE;
11614
11615     if (moveList[currentMove][1] == '@') {
11616         if (appData.highlightLastMove) {
11617             SetHighlights(-1, -1, toX, toY);
11618         }
11619     } else {
11620         fromX = moveList[currentMove][0] - AAA;
11621         fromY = moveList[currentMove][1] - ONE;
11622
11623         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11624
11625         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11626
11627         if (appData.highlightLastMove) {
11628             SetHighlights(fromX, fromY, toX, toY);
11629         }
11630     }
11631     DisplayMove(currentMove);
11632     SendMoveToProgram(currentMove++, &first);
11633     DisplayBothClocks();
11634     DrawPosition(FALSE, boards[currentMove]);
11635     // [HGM] PV info: always display, routine tests if empty
11636     DisplayComment(currentMove - 1, commentList[currentMove]);
11637     return TRUE;
11638 }
11639
11640
11641 int
11642 LoadGameOneMove (ChessMove readAhead)
11643 {
11644     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11645     char promoChar = NULLCHAR;
11646     ChessMove moveType;
11647     char move[MSG_SIZ];
11648     char *p, *q;
11649
11650     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11651         gameMode != AnalyzeMode && gameMode != Training) {
11652         gameFileFP = NULL;
11653         return FALSE;
11654     }
11655
11656     yyboardindex = forwardMostMove;
11657     if (readAhead != EndOfFile) {
11658       moveType = readAhead;
11659     } else {
11660       if (gameFileFP == NULL)
11661           return FALSE;
11662       moveType = (ChessMove) Myylex();
11663     }
11664
11665     done = FALSE;
11666     switch (moveType) {
11667       case Comment:
11668         if (appData.debugMode)
11669           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11670         p = yy_text;
11671
11672         /* append the comment but don't display it */
11673         AppendComment(currentMove, p, FALSE);
11674         return TRUE;
11675
11676       case WhiteCapturesEnPassant:
11677       case BlackCapturesEnPassant:
11678       case WhitePromotion:
11679       case BlackPromotion:
11680       case WhiteNonPromotion:
11681       case BlackNonPromotion:
11682       case NormalMove:
11683       case FirstLeg:
11684       case WhiteKingSideCastle:
11685       case WhiteQueenSideCastle:
11686       case BlackKingSideCastle:
11687       case BlackQueenSideCastle:
11688       case WhiteKingSideCastleWild:
11689       case WhiteQueenSideCastleWild:
11690       case BlackKingSideCastleWild:
11691       case BlackQueenSideCastleWild:
11692       /* PUSH Fabien */
11693       case WhiteHSideCastleFR:
11694       case WhiteASideCastleFR:
11695       case BlackHSideCastleFR:
11696       case BlackASideCastleFR:
11697       /* POP Fabien */
11698         if (appData.debugMode)
11699           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11700         fromX = currentMoveString[0] - AAA;
11701         fromY = currentMoveString[1] - ONE;
11702         toX = currentMoveString[2] - AAA;
11703         toY = currentMoveString[3] - ONE;
11704         promoChar = currentMoveString[4];
11705         if(promoChar == ';') promoChar = NULLCHAR;
11706         break;
11707
11708       case WhiteDrop:
11709       case BlackDrop:
11710         if (appData.debugMode)
11711           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11712         fromX = moveType == WhiteDrop ?
11713           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11714         (int) CharToPiece(ToLower(currentMoveString[0]));
11715         fromY = DROP_RANK;
11716         toX = currentMoveString[2] - AAA;
11717         toY = currentMoveString[3] - ONE;
11718         break;
11719
11720       case WhiteWins:
11721       case BlackWins:
11722       case GameIsDrawn:
11723       case GameUnfinished:
11724         if (appData.debugMode)
11725           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11726         p = strchr(yy_text, '{');
11727         if (p == NULL) p = strchr(yy_text, '(');
11728         if (p == NULL) {
11729             p = yy_text;
11730             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11731         } else {
11732             q = strchr(p, *p == '{' ? '}' : ')');
11733             if (q != NULL) *q = NULLCHAR;
11734             p++;
11735         }
11736         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11737         GameEnds(moveType, p, GE_FILE);
11738         done = TRUE;
11739         if (cmailMsgLoaded) {
11740             ClearHighlights();
11741             flipView = WhiteOnMove(currentMove);
11742             if (moveType == GameUnfinished) flipView = !flipView;
11743             if (appData.debugMode)
11744               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11745         }
11746         break;
11747
11748       case EndOfFile:
11749         if (appData.debugMode)
11750           fprintf(debugFP, "Parser hit end of file\n");
11751         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11752           case MT_NONE:
11753           case MT_CHECK:
11754             break;
11755           case MT_CHECKMATE:
11756           case MT_STAINMATE:
11757             if (WhiteOnMove(currentMove)) {
11758                 GameEnds(BlackWins, "Black mates", GE_FILE);
11759             } else {
11760                 GameEnds(WhiteWins, "White mates", GE_FILE);
11761             }
11762             break;
11763           case MT_STALEMATE:
11764             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11765             break;
11766         }
11767         done = TRUE;
11768         break;
11769
11770       case MoveNumberOne:
11771         if (lastLoadGameStart == GNUChessGame) {
11772             /* GNUChessGames have numbers, but they aren't move numbers */
11773             if (appData.debugMode)
11774               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11775                       yy_text, (int) moveType);
11776             return LoadGameOneMove(EndOfFile); /* tail recursion */
11777         }
11778         /* else fall thru */
11779
11780       case XBoardGame:
11781       case GNUChessGame:
11782       case PGNTag:
11783         /* Reached start of next game in file */
11784         if (appData.debugMode)
11785           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11786         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11787           case MT_NONE:
11788           case MT_CHECK:
11789             break;
11790           case MT_CHECKMATE:
11791           case MT_STAINMATE:
11792             if (WhiteOnMove(currentMove)) {
11793                 GameEnds(BlackWins, "Black mates", GE_FILE);
11794             } else {
11795                 GameEnds(WhiteWins, "White mates", GE_FILE);
11796             }
11797             break;
11798           case MT_STALEMATE:
11799             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11800             break;
11801         }
11802         done = TRUE;
11803         break;
11804
11805       case PositionDiagram:     /* should not happen; ignore */
11806       case ElapsedTime:         /* ignore */
11807       case NAG:                 /* ignore */
11808         if (appData.debugMode)
11809           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11810                   yy_text, (int) moveType);
11811         return LoadGameOneMove(EndOfFile); /* tail recursion */
11812
11813       case IllegalMove:
11814         if (appData.testLegality) {
11815             if (appData.debugMode)
11816               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11817             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11818                     (forwardMostMove / 2) + 1,
11819                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11820             DisplayError(move, 0);
11821             done = TRUE;
11822         } else {
11823             if (appData.debugMode)
11824               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11825                       yy_text, currentMoveString);
11826             fromX = currentMoveString[0] - AAA;
11827             fromY = currentMoveString[1] - ONE;
11828             toX = currentMoveString[2] - AAA;
11829             toY = currentMoveString[3] - ONE;
11830             promoChar = currentMoveString[4];
11831         }
11832         break;
11833
11834       case AmbiguousMove:
11835         if (appData.debugMode)
11836           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11837         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11838                 (forwardMostMove / 2) + 1,
11839                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11840         DisplayError(move, 0);
11841         done = TRUE;
11842         break;
11843
11844       default:
11845       case ImpossibleMove:
11846         if (appData.debugMode)
11847           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11848         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11849                 (forwardMostMove / 2) + 1,
11850                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11851         DisplayError(move, 0);
11852         done = TRUE;
11853         break;
11854     }
11855
11856     if (done) {
11857         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11858             DrawPosition(FALSE, boards[currentMove]);
11859             DisplayBothClocks();
11860             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11861               DisplayComment(currentMove - 1, commentList[currentMove]);
11862         }
11863         (void) StopLoadGameTimer();
11864         gameFileFP = NULL;
11865         cmailOldMove = forwardMostMove;
11866         return FALSE;
11867     } else {
11868         /* currentMoveString is set as a side-effect of yylex */
11869
11870         thinkOutput[0] = NULLCHAR;
11871         MakeMove(fromX, fromY, toX, toY, promoChar);
11872         killX = killY = -1; // [HGM] lion: used up
11873         currentMove = forwardMostMove;
11874         return TRUE;
11875     }
11876 }
11877
11878 /* Load the nth game from the given file */
11879 int
11880 LoadGameFromFile (char *filename, int n, char *title, int useList)
11881 {
11882     FILE *f;
11883     char buf[MSG_SIZ];
11884
11885     if (strcmp(filename, "-") == 0) {
11886         f = stdin;
11887         title = "stdin";
11888     } else {
11889         f = fopen(filename, "rb");
11890         if (f == NULL) {
11891           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11892             DisplayError(buf, errno);
11893             return FALSE;
11894         }
11895     }
11896     if (fseek(f, 0, 0) == -1) {
11897         /* f is not seekable; probably a pipe */
11898         useList = FALSE;
11899     }
11900     if (useList && n == 0) {
11901         int error = GameListBuild(f);
11902         if (error) {
11903             DisplayError(_("Cannot build game list"), error);
11904         } else if (!ListEmpty(&gameList) &&
11905                    ((ListGame *) gameList.tailPred)->number > 1) {
11906             GameListPopUp(f, title);
11907             return TRUE;
11908         }
11909         GameListDestroy();
11910         n = 1;
11911     }
11912     if (n == 0) n = 1;
11913     return LoadGame(f, n, title, FALSE);
11914 }
11915
11916
11917 void
11918 MakeRegisteredMove ()
11919 {
11920     int fromX, fromY, toX, toY;
11921     char promoChar;
11922     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11923         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11924           case CMAIL_MOVE:
11925           case CMAIL_DRAW:
11926             if (appData.debugMode)
11927               fprintf(debugFP, "Restoring %s for game %d\n",
11928                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11929
11930             thinkOutput[0] = NULLCHAR;
11931             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11932             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11933             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11934             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11935             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11936             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11937             MakeMove(fromX, fromY, toX, toY, promoChar);
11938             ShowMove(fromX, fromY, toX, toY);
11939
11940             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11941               case MT_NONE:
11942               case MT_CHECK:
11943                 break;
11944
11945               case MT_CHECKMATE:
11946               case MT_STAINMATE:
11947                 if (WhiteOnMove(currentMove)) {
11948                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11949                 } else {
11950                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11951                 }
11952                 break;
11953
11954               case MT_STALEMATE:
11955                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11956                 break;
11957             }
11958
11959             break;
11960
11961           case CMAIL_RESIGN:
11962             if (WhiteOnMove(currentMove)) {
11963                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11964             } else {
11965                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11966             }
11967             break;
11968
11969           case CMAIL_ACCEPT:
11970             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11971             break;
11972
11973           default:
11974             break;
11975         }
11976     }
11977
11978     return;
11979 }
11980
11981 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11982 int
11983 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11984 {
11985     int retVal;
11986
11987     if (gameNumber > nCmailGames) {
11988         DisplayError(_("No more games in this message"), 0);
11989         return FALSE;
11990     }
11991     if (f == lastLoadGameFP) {
11992         int offset = gameNumber - lastLoadGameNumber;
11993         if (offset == 0) {
11994             cmailMsg[0] = NULLCHAR;
11995             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11996                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11997                 nCmailMovesRegistered--;
11998             }
11999             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12000             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12001                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12002             }
12003         } else {
12004             if (! RegisterMove()) return FALSE;
12005         }
12006     }
12007
12008     retVal = LoadGame(f, gameNumber, title, useList);
12009
12010     /* Make move registered during previous look at this game, if any */
12011     MakeRegisteredMove();
12012
12013     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12014         commentList[currentMove]
12015           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12016         DisplayComment(currentMove - 1, commentList[currentMove]);
12017     }
12018
12019     return retVal;
12020 }
12021
12022 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12023 int
12024 ReloadGame (int offset)
12025 {
12026     int gameNumber = lastLoadGameNumber + offset;
12027     if (lastLoadGameFP == NULL) {
12028         DisplayError(_("No game has been loaded yet"), 0);
12029         return FALSE;
12030     }
12031     if (gameNumber <= 0) {
12032         DisplayError(_("Can't back up any further"), 0);
12033         return FALSE;
12034     }
12035     if (cmailMsgLoaded) {
12036         return CmailLoadGame(lastLoadGameFP, gameNumber,
12037                              lastLoadGameTitle, lastLoadGameUseList);
12038     } else {
12039         return LoadGame(lastLoadGameFP, gameNumber,
12040                         lastLoadGameTitle, lastLoadGameUseList);
12041     }
12042 }
12043
12044 int keys[EmptySquare+1];
12045
12046 int
12047 PositionMatches (Board b1, Board b2)
12048 {
12049     int r, f, sum=0;
12050     switch(appData.searchMode) {
12051         case 1: return CompareWithRights(b1, b2);
12052         case 2:
12053             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12054                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12055             }
12056             return TRUE;
12057         case 3:
12058             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12059               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12060                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12061             }
12062             return sum==0;
12063         case 4:
12064             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12065                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12066             }
12067             return sum==0;
12068     }
12069     return TRUE;
12070 }
12071
12072 #define Q_PROMO  4
12073 #define Q_EP     3
12074 #define Q_BCASTL 2
12075 #define Q_WCASTL 1
12076
12077 int pieceList[256], quickBoard[256];
12078 ChessSquare pieceType[256] = { EmptySquare };
12079 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12080 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12081 int soughtTotal, turn;
12082 Boolean epOK, flipSearch;
12083
12084 typedef struct {
12085     unsigned char piece, to;
12086 } Move;
12087
12088 #define DSIZE (250000)
12089
12090 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12091 Move *moveDatabase = initialSpace;
12092 unsigned int movePtr, dataSize = DSIZE;
12093
12094 int
12095 MakePieceList (Board board, int *counts)
12096 {
12097     int r, f, n=Q_PROMO, total=0;
12098     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12099     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12100         int sq = f + (r<<4);
12101         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12102             quickBoard[sq] = ++n;
12103             pieceList[n] = sq;
12104             pieceType[n] = board[r][f];
12105             counts[board[r][f]]++;
12106             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12107             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12108             total++;
12109         }
12110     }
12111     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12112     return total;
12113 }
12114
12115 void
12116 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12117 {
12118     int sq = fromX + (fromY<<4);
12119     int piece = quickBoard[sq];
12120     quickBoard[sq] = 0;
12121     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12122     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12123         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12124         moveDatabase[movePtr++].piece = Q_WCASTL;
12125         quickBoard[sq] = piece;
12126         piece = quickBoard[from]; quickBoard[from] = 0;
12127         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12128     } else
12129     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12130         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12131         moveDatabase[movePtr++].piece = Q_BCASTL;
12132         quickBoard[sq] = piece;
12133         piece = quickBoard[from]; quickBoard[from] = 0;
12134         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12135     } else
12136     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12137         quickBoard[(fromY<<4)+toX] = 0;
12138         moveDatabase[movePtr].piece = Q_EP;
12139         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12140         moveDatabase[movePtr].to = sq;
12141     } else
12142     if(promoPiece != pieceType[piece]) {
12143         moveDatabase[movePtr++].piece = Q_PROMO;
12144         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12145     }
12146     moveDatabase[movePtr].piece = piece;
12147     quickBoard[sq] = piece;
12148     movePtr++;
12149 }
12150
12151 int
12152 PackGame (Board board)
12153 {
12154     Move *newSpace = NULL;
12155     moveDatabase[movePtr].piece = 0; // terminate previous game
12156     if(movePtr > dataSize) {
12157         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12158         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12159         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12160         if(newSpace) {
12161             int i;
12162             Move *p = moveDatabase, *q = newSpace;
12163             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12164             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12165             moveDatabase = newSpace;
12166         } else { // calloc failed, we must be out of memory. Too bad...
12167             dataSize = 0; // prevent calloc events for all subsequent games
12168             return 0;     // and signal this one isn't cached
12169         }
12170     }
12171     movePtr++;
12172     MakePieceList(board, counts);
12173     return movePtr;
12174 }
12175
12176 int
12177 QuickCompare (Board board, int *minCounts, int *maxCounts)
12178 {   // compare according to search mode
12179     int r, f;
12180     switch(appData.searchMode)
12181     {
12182       case 1: // exact position match
12183         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12184         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12185             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12186         }
12187         break;
12188       case 2: // can have extra material on empty squares
12189         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12190             if(board[r][f] == EmptySquare) continue;
12191             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12192         }
12193         break;
12194       case 3: // material with exact Pawn structure
12195         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12196             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12197             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12198         } // fall through to material comparison
12199       case 4: // exact material
12200         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12201         break;
12202       case 6: // material range with given imbalance
12203         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12204         // fall through to range comparison
12205       case 5: // material range
12206         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12207     }
12208     return TRUE;
12209 }
12210
12211 int
12212 QuickScan (Board board, Move *move)
12213 {   // reconstruct game,and compare all positions in it
12214     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12215     do {
12216         int piece = move->piece;
12217         int to = move->to, from = pieceList[piece];
12218         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12219           if(!piece) return -1;
12220           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12221             piece = (++move)->piece;
12222             from = pieceList[piece];
12223             counts[pieceType[piece]]--;
12224             pieceType[piece] = (ChessSquare) move->to;
12225             counts[move->to]++;
12226           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12227             counts[pieceType[quickBoard[to]]]--;
12228             quickBoard[to] = 0; total--;
12229             move++;
12230             continue;
12231           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12232             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12233             from  = pieceList[piece]; // so this must be King
12234             quickBoard[from] = 0;
12235             pieceList[piece] = to;
12236             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12237             quickBoard[from] = 0; // rook
12238             quickBoard[to] = piece;
12239             to = move->to; piece = move->piece;
12240             goto aftercastle;
12241           }
12242         }
12243         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12244         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12245         quickBoard[from] = 0;
12246       aftercastle:
12247         quickBoard[to] = piece;
12248         pieceList[piece] = to;
12249         cnt++; turn ^= 3;
12250         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12251            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12252            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12253                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12254           ) {
12255             static int lastCounts[EmptySquare+1];
12256             int i;
12257             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12258             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12259         } else stretch = 0;
12260         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12261         move++;
12262     } while(1);
12263 }
12264
12265 void
12266 InitSearch ()
12267 {
12268     int r, f;
12269     flipSearch = FALSE;
12270     CopyBoard(soughtBoard, boards[currentMove]);
12271     soughtTotal = MakePieceList(soughtBoard, maxSought);
12272     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12273     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12274     CopyBoard(reverseBoard, boards[currentMove]);
12275     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12276         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12277         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12278         reverseBoard[r][f] = piece;
12279     }
12280     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12281     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12282     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12283                  || (boards[currentMove][CASTLING][2] == NoRights ||
12284                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12285                  && (boards[currentMove][CASTLING][5] == NoRights ||
12286                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12287       ) {
12288         flipSearch = TRUE;
12289         CopyBoard(flipBoard, soughtBoard);
12290         CopyBoard(rotateBoard, reverseBoard);
12291         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12292             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12293             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12294         }
12295     }
12296     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12297     if(appData.searchMode >= 5) {
12298         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12299         MakePieceList(soughtBoard, minSought);
12300         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12301     }
12302     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12303         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12304 }
12305
12306 GameInfo dummyInfo;
12307 static int creatingBook;
12308
12309 int
12310 GameContainsPosition (FILE *f, ListGame *lg)
12311 {
12312     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12313     int fromX, fromY, toX, toY;
12314     char promoChar;
12315     static int initDone=FALSE;
12316
12317     // weed out games based on numerical tag comparison
12318     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12319     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12320     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12321     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12322     if(!initDone) {
12323         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12324         initDone = TRUE;
12325     }
12326     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12327     else CopyBoard(boards[scratch], initialPosition); // default start position
12328     if(lg->moves) {
12329         turn = btm + 1;
12330         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12331         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12332     }
12333     if(btm) plyNr++;
12334     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12335     fseek(f, lg->offset, 0);
12336     yynewfile(f);
12337     while(1) {
12338         yyboardindex = scratch;
12339         quickFlag = plyNr+1;
12340         next = Myylex();
12341         quickFlag = 0;
12342         switch(next) {
12343             case PGNTag:
12344                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12345             default:
12346                 continue;
12347
12348             case XBoardGame:
12349             case GNUChessGame:
12350                 if(plyNr) return -1; // after we have seen moves, this is for new game
12351               continue;
12352
12353             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12354             case ImpossibleMove:
12355             case WhiteWins: // game ends here with these four
12356             case BlackWins:
12357             case GameIsDrawn:
12358             case GameUnfinished:
12359                 return -1;
12360
12361             case IllegalMove:
12362                 if(appData.testLegality) return -1;
12363             case WhiteCapturesEnPassant:
12364             case BlackCapturesEnPassant:
12365             case WhitePromotion:
12366             case BlackPromotion:
12367             case WhiteNonPromotion:
12368             case BlackNonPromotion:
12369             case NormalMove:
12370             case FirstLeg:
12371             case WhiteKingSideCastle:
12372             case WhiteQueenSideCastle:
12373             case BlackKingSideCastle:
12374             case BlackQueenSideCastle:
12375             case WhiteKingSideCastleWild:
12376             case WhiteQueenSideCastleWild:
12377             case BlackKingSideCastleWild:
12378             case BlackQueenSideCastleWild:
12379             case WhiteHSideCastleFR:
12380             case WhiteASideCastleFR:
12381             case BlackHSideCastleFR:
12382             case BlackASideCastleFR:
12383                 fromX = currentMoveString[0] - AAA;
12384                 fromY = currentMoveString[1] - ONE;
12385                 toX = currentMoveString[2] - AAA;
12386                 toY = currentMoveString[3] - ONE;
12387                 promoChar = currentMoveString[4];
12388                 break;
12389             case WhiteDrop:
12390             case BlackDrop:
12391                 fromX = next == WhiteDrop ?
12392                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12393                   (int) CharToPiece(ToLower(currentMoveString[0]));
12394                 fromY = DROP_RANK;
12395                 toX = currentMoveString[2] - AAA;
12396                 toY = currentMoveString[3] - ONE;
12397                 promoChar = 0;
12398                 break;
12399         }
12400         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12401         plyNr++;
12402         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12403         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12404         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12405         if(appData.findMirror) {
12406             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12407             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12408         }
12409     }
12410 }
12411
12412 /* Load the nth game from open file f */
12413 int
12414 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12415 {
12416     ChessMove cm;
12417     char buf[MSG_SIZ];
12418     int gn = gameNumber;
12419     ListGame *lg = NULL;
12420     int numPGNTags = 0;
12421     int err, pos = -1;
12422     GameMode oldGameMode;
12423     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12424
12425     if (appData.debugMode)
12426         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12427
12428     if (gameMode == Training )
12429         SetTrainingModeOff();
12430
12431     oldGameMode = gameMode;
12432     if (gameMode != BeginningOfGame) {
12433       Reset(FALSE, TRUE);
12434     }
12435     killX = killY = -1; // [HGM] lion: in case we did not Reset
12436
12437     gameFileFP = f;
12438     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12439         fclose(lastLoadGameFP);
12440     }
12441
12442     if (useList) {
12443         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12444
12445         if (lg) {
12446             fseek(f, lg->offset, 0);
12447             GameListHighlight(gameNumber);
12448             pos = lg->position;
12449             gn = 1;
12450         }
12451         else {
12452             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12453               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12454             else
12455             DisplayError(_("Game number out of range"), 0);
12456             return FALSE;
12457         }
12458     } else {
12459         GameListDestroy();
12460         if (fseek(f, 0, 0) == -1) {
12461             if (f == lastLoadGameFP ?
12462                 gameNumber == lastLoadGameNumber + 1 :
12463                 gameNumber == 1) {
12464                 gn = 1;
12465             } else {
12466                 DisplayError(_("Can't seek on game file"), 0);
12467                 return FALSE;
12468             }
12469         }
12470     }
12471     lastLoadGameFP = f;
12472     lastLoadGameNumber = gameNumber;
12473     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12474     lastLoadGameUseList = useList;
12475
12476     yynewfile(f);
12477
12478     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12479       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12480                 lg->gameInfo.black);
12481             DisplayTitle(buf);
12482     } else if (*title != NULLCHAR) {
12483         if (gameNumber > 1) {
12484           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12485             DisplayTitle(buf);
12486         } else {
12487             DisplayTitle(title);
12488         }
12489     }
12490
12491     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12492         gameMode = PlayFromGameFile;
12493         ModeHighlight();
12494     }
12495
12496     currentMove = forwardMostMove = backwardMostMove = 0;
12497     CopyBoard(boards[0], initialPosition);
12498     StopClocks();
12499
12500     /*
12501      * Skip the first gn-1 games in the file.
12502      * Also skip over anything that precedes an identifiable
12503      * start of game marker, to avoid being confused by
12504      * garbage at the start of the file.  Currently
12505      * recognized start of game markers are the move number "1",
12506      * the pattern "gnuchess .* game", the pattern
12507      * "^[#;%] [^ ]* game file", and a PGN tag block.
12508      * A game that starts with one of the latter two patterns
12509      * will also have a move number 1, possibly
12510      * following a position diagram.
12511      * 5-4-02: Let's try being more lenient and allowing a game to
12512      * start with an unnumbered move.  Does that break anything?
12513      */
12514     cm = lastLoadGameStart = EndOfFile;
12515     while (gn > 0) {
12516         yyboardindex = forwardMostMove;
12517         cm = (ChessMove) Myylex();
12518         switch (cm) {
12519           case EndOfFile:
12520             if (cmailMsgLoaded) {
12521                 nCmailGames = CMAIL_MAX_GAMES - gn;
12522             } else {
12523                 Reset(TRUE, TRUE);
12524                 DisplayError(_("Game not found in file"), 0);
12525             }
12526             return FALSE;
12527
12528           case GNUChessGame:
12529           case XBoardGame:
12530             gn--;
12531             lastLoadGameStart = cm;
12532             break;
12533
12534           case MoveNumberOne:
12535             switch (lastLoadGameStart) {
12536               case GNUChessGame:
12537               case XBoardGame:
12538               case PGNTag:
12539                 break;
12540               case MoveNumberOne:
12541               case EndOfFile:
12542                 gn--;           /* count this game */
12543                 lastLoadGameStart = cm;
12544                 break;
12545               default:
12546                 /* impossible */
12547                 break;
12548             }
12549             break;
12550
12551           case PGNTag:
12552             switch (lastLoadGameStart) {
12553               case GNUChessGame:
12554               case PGNTag:
12555               case MoveNumberOne:
12556               case EndOfFile:
12557                 gn--;           /* count this game */
12558                 lastLoadGameStart = cm;
12559                 break;
12560               case XBoardGame:
12561                 lastLoadGameStart = cm; /* game counted already */
12562                 break;
12563               default:
12564                 /* impossible */
12565                 break;
12566             }
12567             if (gn > 0) {
12568                 do {
12569                     yyboardindex = forwardMostMove;
12570                     cm = (ChessMove) Myylex();
12571                 } while (cm == PGNTag || cm == Comment);
12572             }
12573             break;
12574
12575           case WhiteWins:
12576           case BlackWins:
12577           case GameIsDrawn:
12578             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12579                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12580                     != CMAIL_OLD_RESULT) {
12581                     nCmailResults ++ ;
12582                     cmailResult[  CMAIL_MAX_GAMES
12583                                 - gn - 1] = CMAIL_OLD_RESULT;
12584                 }
12585             }
12586             break;
12587
12588           case NormalMove:
12589           case FirstLeg:
12590             /* Only a NormalMove can be at the start of a game
12591              * without a position diagram. */
12592             if (lastLoadGameStart == EndOfFile ) {
12593               gn--;
12594               lastLoadGameStart = MoveNumberOne;
12595             }
12596             break;
12597
12598           default:
12599             break;
12600         }
12601     }
12602
12603     if (appData.debugMode)
12604       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12605
12606     if (cm == XBoardGame) {
12607         /* Skip any header junk before position diagram and/or move 1 */
12608         for (;;) {
12609             yyboardindex = forwardMostMove;
12610             cm = (ChessMove) Myylex();
12611
12612             if (cm == EndOfFile ||
12613                 cm == GNUChessGame || cm == XBoardGame) {
12614                 /* Empty game; pretend end-of-file and handle later */
12615                 cm = EndOfFile;
12616                 break;
12617             }
12618
12619             if (cm == MoveNumberOne || cm == PositionDiagram ||
12620                 cm == PGNTag || cm == Comment)
12621               break;
12622         }
12623     } else if (cm == GNUChessGame) {
12624         if (gameInfo.event != NULL) {
12625             free(gameInfo.event);
12626         }
12627         gameInfo.event = StrSave(yy_text);
12628     }
12629
12630     startedFromSetupPosition = FALSE;
12631     while (cm == PGNTag) {
12632         if (appData.debugMode)
12633           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12634         err = ParsePGNTag(yy_text, &gameInfo);
12635         if (!err) numPGNTags++;
12636
12637         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12638         if(gameInfo.variant != oldVariant) {
12639             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12640             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12641             InitPosition(TRUE);
12642             oldVariant = gameInfo.variant;
12643             if (appData.debugMode)
12644               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12645         }
12646
12647
12648         if (gameInfo.fen != NULL) {
12649           Board initial_position;
12650           startedFromSetupPosition = TRUE;
12651           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12652             Reset(TRUE, TRUE);
12653             DisplayError(_("Bad FEN position in file"), 0);
12654             return FALSE;
12655           }
12656           CopyBoard(boards[0], initial_position);
12657           if (blackPlaysFirst) {
12658             currentMove = forwardMostMove = backwardMostMove = 1;
12659             CopyBoard(boards[1], initial_position);
12660             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12661             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12662             timeRemaining[0][1] = whiteTimeRemaining;
12663             timeRemaining[1][1] = blackTimeRemaining;
12664             if (commentList[0] != NULL) {
12665               commentList[1] = commentList[0];
12666               commentList[0] = NULL;
12667             }
12668           } else {
12669             currentMove = forwardMostMove = backwardMostMove = 0;
12670           }
12671           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12672           {   int i;
12673               initialRulePlies = FENrulePlies;
12674               for( i=0; i< nrCastlingRights; i++ )
12675                   initialRights[i] = initial_position[CASTLING][i];
12676           }
12677           yyboardindex = forwardMostMove;
12678           free(gameInfo.fen);
12679           gameInfo.fen = NULL;
12680         }
12681
12682         yyboardindex = forwardMostMove;
12683         cm = (ChessMove) Myylex();
12684
12685         /* Handle comments interspersed among the tags */
12686         while (cm == Comment) {
12687             char *p;
12688             if (appData.debugMode)
12689               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12690             p = yy_text;
12691             AppendComment(currentMove, p, FALSE);
12692             yyboardindex = forwardMostMove;
12693             cm = (ChessMove) Myylex();
12694         }
12695     }
12696
12697     /* don't rely on existence of Event tag since if game was
12698      * pasted from clipboard the Event tag may not exist
12699      */
12700     if (numPGNTags > 0){
12701         char *tags;
12702         if (gameInfo.variant == VariantNormal) {
12703           VariantClass v = StringToVariant(gameInfo.event);
12704           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12705           if(v < VariantShogi) gameInfo.variant = v;
12706         }
12707         if (!matchMode) {
12708           if( appData.autoDisplayTags ) {
12709             tags = PGNTags(&gameInfo);
12710             TagsPopUp(tags, CmailMsg());
12711             free(tags);
12712           }
12713         }
12714     } else {
12715         /* Make something up, but don't display it now */
12716         SetGameInfo();
12717         TagsPopDown();
12718     }
12719
12720     if (cm == PositionDiagram) {
12721         int i, j;
12722         char *p;
12723         Board initial_position;
12724
12725         if (appData.debugMode)
12726           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12727
12728         if (!startedFromSetupPosition) {
12729             p = yy_text;
12730             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12731               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12732                 switch (*p) {
12733                   case '{':
12734                   case '[':
12735                   case '-':
12736                   case ' ':
12737                   case '\t':
12738                   case '\n':
12739                   case '\r':
12740                     break;
12741                   default:
12742                     initial_position[i][j++] = CharToPiece(*p);
12743                     break;
12744                 }
12745             while (*p == ' ' || *p == '\t' ||
12746                    *p == '\n' || *p == '\r') p++;
12747
12748             if (strncmp(p, "black", strlen("black"))==0)
12749               blackPlaysFirst = TRUE;
12750             else
12751               blackPlaysFirst = FALSE;
12752             startedFromSetupPosition = TRUE;
12753
12754             CopyBoard(boards[0], initial_position);
12755             if (blackPlaysFirst) {
12756                 currentMove = forwardMostMove = backwardMostMove = 1;
12757                 CopyBoard(boards[1], initial_position);
12758                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12759                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12760                 timeRemaining[0][1] = whiteTimeRemaining;
12761                 timeRemaining[1][1] = blackTimeRemaining;
12762                 if (commentList[0] != NULL) {
12763                     commentList[1] = commentList[0];
12764                     commentList[0] = NULL;
12765                 }
12766             } else {
12767                 currentMove = forwardMostMove = backwardMostMove = 0;
12768             }
12769         }
12770         yyboardindex = forwardMostMove;
12771         cm = (ChessMove) Myylex();
12772     }
12773
12774   if(!creatingBook) {
12775     if (first.pr == NoProc) {
12776         StartChessProgram(&first);
12777     }
12778     InitChessProgram(&first, FALSE);
12779     SendToProgram("force\n", &first);
12780     if (startedFromSetupPosition) {
12781         SendBoard(&first, forwardMostMove);
12782     if (appData.debugMode) {
12783         fprintf(debugFP, "Load Game\n");
12784     }
12785         DisplayBothClocks();
12786     }
12787   }
12788
12789     /* [HGM] server: flag to write setup moves in broadcast file as one */
12790     loadFlag = appData.suppressLoadMoves;
12791
12792     while (cm == Comment) {
12793         char *p;
12794         if (appData.debugMode)
12795           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12796         p = yy_text;
12797         AppendComment(currentMove, p, FALSE);
12798         yyboardindex = forwardMostMove;
12799         cm = (ChessMove) Myylex();
12800     }
12801
12802     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12803         cm == WhiteWins || cm == BlackWins ||
12804         cm == GameIsDrawn || cm == GameUnfinished) {
12805         DisplayMessage("", _("No moves in game"));
12806         if (cmailMsgLoaded) {
12807             if (appData.debugMode)
12808               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12809             ClearHighlights();
12810             flipView = FALSE;
12811         }
12812         DrawPosition(FALSE, boards[currentMove]);
12813         DisplayBothClocks();
12814         gameMode = EditGame;
12815         ModeHighlight();
12816         gameFileFP = NULL;
12817         cmailOldMove = 0;
12818         return TRUE;
12819     }
12820
12821     // [HGM] PV info: routine tests if comment empty
12822     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12823         DisplayComment(currentMove - 1, commentList[currentMove]);
12824     }
12825     if (!matchMode && appData.timeDelay != 0)
12826       DrawPosition(FALSE, boards[currentMove]);
12827
12828     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12829       programStats.ok_to_send = 1;
12830     }
12831
12832     /* if the first token after the PGN tags is a move
12833      * and not move number 1, retrieve it from the parser
12834      */
12835     if (cm != MoveNumberOne)
12836         LoadGameOneMove(cm);
12837
12838     /* load the remaining moves from the file */
12839     while (LoadGameOneMove(EndOfFile)) {
12840       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12841       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12842     }
12843
12844     /* rewind to the start of the game */
12845     currentMove = backwardMostMove;
12846
12847     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12848
12849     if (oldGameMode == AnalyzeFile) {
12850       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12851       AnalyzeFileEvent();
12852     } else
12853     if (oldGameMode == AnalyzeMode) {
12854       AnalyzeFileEvent();
12855     }
12856
12857     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12858         long int w, b; // [HGM] adjourn: restore saved clock times
12859         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12860         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12861             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12862             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12863         }
12864     }
12865
12866     if(creatingBook) return TRUE;
12867     if (!matchMode && pos > 0) {
12868         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12869     } else
12870     if (matchMode || appData.timeDelay == 0) {
12871       ToEndEvent();
12872     } else if (appData.timeDelay > 0) {
12873       AutoPlayGameLoop();
12874     }
12875
12876     if (appData.debugMode)
12877         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12878
12879     loadFlag = 0; /* [HGM] true game starts */
12880     return TRUE;
12881 }
12882
12883 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12884 int
12885 ReloadPosition (int offset)
12886 {
12887     int positionNumber = lastLoadPositionNumber + offset;
12888     if (lastLoadPositionFP == NULL) {
12889         DisplayError(_("No position has been loaded yet"), 0);
12890         return FALSE;
12891     }
12892     if (positionNumber <= 0) {
12893         DisplayError(_("Can't back up any further"), 0);
12894         return FALSE;
12895     }
12896     return LoadPosition(lastLoadPositionFP, positionNumber,
12897                         lastLoadPositionTitle);
12898 }
12899
12900 /* Load the nth position from the given file */
12901 int
12902 LoadPositionFromFile (char *filename, int n, char *title)
12903 {
12904     FILE *f;
12905     char buf[MSG_SIZ];
12906
12907     if (strcmp(filename, "-") == 0) {
12908         return LoadPosition(stdin, n, "stdin");
12909     } else {
12910         f = fopen(filename, "rb");
12911         if (f == NULL) {
12912             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12913             DisplayError(buf, errno);
12914             return FALSE;
12915         } else {
12916             return LoadPosition(f, n, title);
12917         }
12918     }
12919 }
12920
12921 /* Load the nth position from the given open file, and close it */
12922 int
12923 LoadPosition (FILE *f, int positionNumber, char *title)
12924 {
12925     char *p, line[MSG_SIZ];
12926     Board initial_position;
12927     int i, j, fenMode, pn;
12928
12929     if (gameMode == Training )
12930         SetTrainingModeOff();
12931
12932     if (gameMode != BeginningOfGame) {
12933         Reset(FALSE, TRUE);
12934     }
12935     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12936         fclose(lastLoadPositionFP);
12937     }
12938     if (positionNumber == 0) positionNumber = 1;
12939     lastLoadPositionFP = f;
12940     lastLoadPositionNumber = positionNumber;
12941     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12942     if (first.pr == NoProc && !appData.noChessProgram) {
12943       StartChessProgram(&first);
12944       InitChessProgram(&first, FALSE);
12945     }
12946     pn = positionNumber;
12947     if (positionNumber < 0) {
12948         /* Negative position number means to seek to that byte offset */
12949         if (fseek(f, -positionNumber, 0) == -1) {
12950             DisplayError(_("Can't seek on position file"), 0);
12951             return FALSE;
12952         };
12953         pn = 1;
12954     } else {
12955         if (fseek(f, 0, 0) == -1) {
12956             if (f == lastLoadPositionFP ?
12957                 positionNumber == lastLoadPositionNumber + 1 :
12958                 positionNumber == 1) {
12959                 pn = 1;
12960             } else {
12961                 DisplayError(_("Can't seek on position file"), 0);
12962                 return FALSE;
12963             }
12964         }
12965     }
12966     /* See if this file is FEN or old-style xboard */
12967     if (fgets(line, MSG_SIZ, f) == NULL) {
12968         DisplayError(_("Position not found in file"), 0);
12969         return FALSE;
12970     }
12971     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12972     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12973
12974     if (pn >= 2) {
12975         if (fenMode || line[0] == '#') pn--;
12976         while (pn > 0) {
12977             /* skip positions before number pn */
12978             if (fgets(line, MSG_SIZ, f) == NULL) {
12979                 Reset(TRUE, TRUE);
12980                 DisplayError(_("Position not found in file"), 0);
12981                 return FALSE;
12982             }
12983             if (fenMode || line[0] == '#') pn--;
12984         }
12985     }
12986
12987     if (fenMode) {
12988         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12989             DisplayError(_("Bad FEN position in file"), 0);
12990             return FALSE;
12991         }
12992     } else {
12993         (void) fgets(line, MSG_SIZ, f);
12994         (void) fgets(line, MSG_SIZ, f);
12995
12996         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12997             (void) fgets(line, MSG_SIZ, f);
12998             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12999                 if (*p == ' ')
13000                   continue;
13001                 initial_position[i][j++] = CharToPiece(*p);
13002             }
13003         }
13004
13005         blackPlaysFirst = FALSE;
13006         if (!feof(f)) {
13007             (void) fgets(line, MSG_SIZ, f);
13008             if (strncmp(line, "black", strlen("black"))==0)
13009               blackPlaysFirst = TRUE;
13010         }
13011     }
13012     startedFromSetupPosition = TRUE;
13013
13014     CopyBoard(boards[0], initial_position);
13015     if (blackPlaysFirst) {
13016         currentMove = forwardMostMove = backwardMostMove = 1;
13017         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13018         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13019         CopyBoard(boards[1], initial_position);
13020         DisplayMessage("", _("Black to play"));
13021     } else {
13022         currentMove = forwardMostMove = backwardMostMove = 0;
13023         DisplayMessage("", _("White to play"));
13024     }
13025     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13026     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13027         SendToProgram("force\n", &first);
13028         SendBoard(&first, forwardMostMove);
13029     }
13030     if (appData.debugMode) {
13031 int i, j;
13032   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13033   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13034         fprintf(debugFP, "Load Position\n");
13035     }
13036
13037     if (positionNumber > 1) {
13038       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13039         DisplayTitle(line);
13040     } else {
13041         DisplayTitle(title);
13042     }
13043     gameMode = EditGame;
13044     ModeHighlight();
13045     ResetClocks();
13046     timeRemaining[0][1] = whiteTimeRemaining;
13047     timeRemaining[1][1] = blackTimeRemaining;
13048     DrawPosition(FALSE, boards[currentMove]);
13049
13050     return TRUE;
13051 }
13052
13053
13054 void
13055 CopyPlayerNameIntoFileName (char **dest, char *src)
13056 {
13057     while (*src != NULLCHAR && *src != ',') {
13058         if (*src == ' ') {
13059             *(*dest)++ = '_';
13060             src++;
13061         } else {
13062             *(*dest)++ = *src++;
13063         }
13064     }
13065 }
13066
13067 char *
13068 DefaultFileName (char *ext)
13069 {
13070     static char def[MSG_SIZ];
13071     char *p;
13072
13073     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13074         p = def;
13075         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13076         *p++ = '-';
13077         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13078         *p++ = '.';
13079         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13080     } else {
13081         def[0] = NULLCHAR;
13082     }
13083     return def;
13084 }
13085
13086 /* Save the current game to the given file */
13087 int
13088 SaveGameToFile (char *filename, int append)
13089 {
13090     FILE *f;
13091     char buf[MSG_SIZ];
13092     int result, i, t,tot=0;
13093
13094     if (strcmp(filename, "-") == 0) {
13095         return SaveGame(stdout, 0, NULL);
13096     } else {
13097         for(i=0; i<10; i++) { // upto 10 tries
13098              f = fopen(filename, append ? "a" : "w");
13099              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13100              if(f || errno != 13) break;
13101              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13102              tot += t;
13103         }
13104         if (f == NULL) {
13105             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13106             DisplayError(buf, errno);
13107             return FALSE;
13108         } else {
13109             safeStrCpy(buf, lastMsg, MSG_SIZ);
13110             DisplayMessage(_("Waiting for access to save file"), "");
13111             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13112             DisplayMessage(_("Saving game"), "");
13113             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13114             result = SaveGame(f, 0, NULL);
13115             DisplayMessage(buf, "");
13116             return result;
13117         }
13118     }
13119 }
13120
13121 char *
13122 SavePart (char *str)
13123 {
13124     static char buf[MSG_SIZ];
13125     char *p;
13126
13127     p = strchr(str, ' ');
13128     if (p == NULL) return str;
13129     strncpy(buf, str, p - str);
13130     buf[p - str] = NULLCHAR;
13131     return buf;
13132 }
13133
13134 #define PGN_MAX_LINE 75
13135
13136 #define PGN_SIDE_WHITE  0
13137 #define PGN_SIDE_BLACK  1
13138
13139 static int
13140 FindFirstMoveOutOfBook (int side)
13141 {
13142     int result = -1;
13143
13144     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13145         int index = backwardMostMove;
13146         int has_book_hit = 0;
13147
13148         if( (index % 2) != side ) {
13149             index++;
13150         }
13151
13152         while( index < forwardMostMove ) {
13153             /* Check to see if engine is in book */
13154             int depth = pvInfoList[index].depth;
13155             int score = pvInfoList[index].score;
13156             int in_book = 0;
13157
13158             if( depth <= 2 ) {
13159                 in_book = 1;
13160             }
13161             else if( score == 0 && depth == 63 ) {
13162                 in_book = 1; /* Zappa */
13163             }
13164             else if( score == 2 && depth == 99 ) {
13165                 in_book = 1; /* Abrok */
13166             }
13167
13168             has_book_hit += in_book;
13169
13170             if( ! in_book ) {
13171                 result = index;
13172
13173                 break;
13174             }
13175
13176             index += 2;
13177         }
13178     }
13179
13180     return result;
13181 }
13182
13183 void
13184 GetOutOfBookInfo (char * buf)
13185 {
13186     int oob[2];
13187     int i;
13188     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13189
13190     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13191     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13192
13193     *buf = '\0';
13194
13195     if( oob[0] >= 0 || oob[1] >= 0 ) {
13196         for( i=0; i<2; i++ ) {
13197             int idx = oob[i];
13198
13199             if( idx >= 0 ) {
13200                 if( i > 0 && oob[0] >= 0 ) {
13201                     strcat( buf, "   " );
13202                 }
13203
13204                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13205                 sprintf( buf+strlen(buf), "%s%.2f",
13206                     pvInfoList[idx].score >= 0 ? "+" : "",
13207                     pvInfoList[idx].score / 100.0 );
13208             }
13209         }
13210     }
13211 }
13212
13213 /* Save game in PGN style and close the file */
13214 int
13215 SaveGamePGN (FILE *f)
13216 {
13217     int i, offset, linelen, newblock;
13218 //    char *movetext;
13219     char numtext[32];
13220     int movelen, numlen, blank;
13221     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13222
13223     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13224
13225     PrintPGNTags(f, &gameInfo);
13226
13227     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13228
13229     if (backwardMostMove > 0 || startedFromSetupPosition) {
13230         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13231         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13232         fprintf(f, "\n{--------------\n");
13233         PrintPosition(f, backwardMostMove);
13234         fprintf(f, "--------------}\n");
13235         free(fen);
13236     }
13237     else {
13238         /* [AS] Out of book annotation */
13239         if( appData.saveOutOfBookInfo ) {
13240             char buf[64];
13241
13242             GetOutOfBookInfo( buf );
13243
13244             if( buf[0] != '\0' ) {
13245                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13246             }
13247         }
13248
13249         fprintf(f, "\n");
13250     }
13251
13252     i = backwardMostMove;
13253     linelen = 0;
13254     newblock = TRUE;
13255
13256     while (i < forwardMostMove) {
13257         /* Print comments preceding this move */
13258         if (commentList[i] != NULL) {
13259             if (linelen > 0) fprintf(f, "\n");
13260             fprintf(f, "%s", commentList[i]);
13261             linelen = 0;
13262             newblock = TRUE;
13263         }
13264
13265         /* Format move number */
13266         if ((i % 2) == 0)
13267           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13268         else
13269           if (newblock)
13270             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13271           else
13272             numtext[0] = NULLCHAR;
13273
13274         numlen = strlen(numtext);
13275         newblock = FALSE;
13276
13277         /* Print move number */
13278         blank = linelen > 0 && numlen > 0;
13279         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13280             fprintf(f, "\n");
13281             linelen = 0;
13282             blank = 0;
13283         }
13284         if (blank) {
13285             fprintf(f, " ");
13286             linelen++;
13287         }
13288         fprintf(f, "%s", numtext);
13289         linelen += numlen;
13290
13291         /* Get move */
13292         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13293         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13294
13295         /* Print move */
13296         blank = linelen > 0 && movelen > 0;
13297         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13298             fprintf(f, "\n");
13299             linelen = 0;
13300             blank = 0;
13301         }
13302         if (blank) {
13303             fprintf(f, " ");
13304             linelen++;
13305         }
13306         fprintf(f, "%s", move_buffer);
13307         linelen += movelen;
13308
13309         /* [AS] Add PV info if present */
13310         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13311             /* [HGM] add time */
13312             char buf[MSG_SIZ]; int seconds;
13313
13314             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13315
13316             if( seconds <= 0)
13317               buf[0] = 0;
13318             else
13319               if( seconds < 30 )
13320                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13321               else
13322                 {
13323                   seconds = (seconds + 4)/10; // round to full seconds
13324                   if( seconds < 60 )
13325                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13326                   else
13327                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13328                 }
13329
13330             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13331                       pvInfoList[i].score >= 0 ? "+" : "",
13332                       pvInfoList[i].score / 100.0,
13333                       pvInfoList[i].depth,
13334                       buf );
13335
13336             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13337
13338             /* Print score/depth */
13339             blank = linelen > 0 && movelen > 0;
13340             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13341                 fprintf(f, "\n");
13342                 linelen = 0;
13343                 blank = 0;
13344             }
13345             if (blank) {
13346                 fprintf(f, " ");
13347                 linelen++;
13348             }
13349             fprintf(f, "%s", move_buffer);
13350             linelen += movelen;
13351         }
13352
13353         i++;
13354     }
13355
13356     /* Start a new line */
13357     if (linelen > 0) fprintf(f, "\n");
13358
13359     /* Print comments after last move */
13360     if (commentList[i] != NULL) {
13361         fprintf(f, "%s\n", commentList[i]);
13362     }
13363
13364     /* Print result */
13365     if (gameInfo.resultDetails != NULL &&
13366         gameInfo.resultDetails[0] != NULLCHAR) {
13367         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13368         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13369            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13370             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13371         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13372     } else {
13373         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13374     }
13375
13376     fclose(f);
13377     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13378     return TRUE;
13379 }
13380
13381 /* Save game in old style and close the file */
13382 int
13383 SaveGameOldStyle (FILE *f)
13384 {
13385     int i, offset;
13386     time_t tm;
13387
13388     tm = time((time_t *) NULL);
13389
13390     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13391     PrintOpponents(f);
13392
13393     if (backwardMostMove > 0 || startedFromSetupPosition) {
13394         fprintf(f, "\n[--------------\n");
13395         PrintPosition(f, backwardMostMove);
13396         fprintf(f, "--------------]\n");
13397     } else {
13398         fprintf(f, "\n");
13399     }
13400
13401     i = backwardMostMove;
13402     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13403
13404     while (i < forwardMostMove) {
13405         if (commentList[i] != NULL) {
13406             fprintf(f, "[%s]\n", commentList[i]);
13407         }
13408
13409         if ((i % 2) == 1) {
13410             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13411             i++;
13412         } else {
13413             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13414             i++;
13415             if (commentList[i] != NULL) {
13416                 fprintf(f, "\n");
13417                 continue;
13418             }
13419             if (i >= forwardMostMove) {
13420                 fprintf(f, "\n");
13421                 break;
13422             }
13423             fprintf(f, "%s\n", parseList[i]);
13424             i++;
13425         }
13426     }
13427
13428     if (commentList[i] != NULL) {
13429         fprintf(f, "[%s]\n", commentList[i]);
13430     }
13431
13432     /* This isn't really the old style, but it's close enough */
13433     if (gameInfo.resultDetails != NULL &&
13434         gameInfo.resultDetails[0] != NULLCHAR) {
13435         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13436                 gameInfo.resultDetails);
13437     } else {
13438         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13439     }
13440
13441     fclose(f);
13442     return TRUE;
13443 }
13444
13445 /* Save the current game to open file f and close the file */
13446 int
13447 SaveGame (FILE *f, int dummy, char *dummy2)
13448 {
13449     if (gameMode == EditPosition) EditPositionDone(TRUE);
13450     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13451     if (appData.oldSaveStyle)
13452       return SaveGameOldStyle(f);
13453     else
13454       return SaveGamePGN(f);
13455 }
13456
13457 /* Save the current position to the given file */
13458 int
13459 SavePositionToFile (char *filename)
13460 {
13461     FILE *f;
13462     char buf[MSG_SIZ];
13463
13464     if (strcmp(filename, "-") == 0) {
13465         return SavePosition(stdout, 0, NULL);
13466     } else {
13467         f = fopen(filename, "a");
13468         if (f == NULL) {
13469             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13470             DisplayError(buf, errno);
13471             return FALSE;
13472         } else {
13473             safeStrCpy(buf, lastMsg, MSG_SIZ);
13474             DisplayMessage(_("Waiting for access to save file"), "");
13475             flock(fileno(f), LOCK_EX); // [HGM] lock
13476             DisplayMessage(_("Saving position"), "");
13477             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13478             SavePosition(f, 0, NULL);
13479             DisplayMessage(buf, "");
13480             return TRUE;
13481         }
13482     }
13483 }
13484
13485 /* Save the current position to the given open file and close the file */
13486 int
13487 SavePosition (FILE *f, int dummy, char *dummy2)
13488 {
13489     time_t tm;
13490     char *fen;
13491
13492     if (gameMode == EditPosition) EditPositionDone(TRUE);
13493     if (appData.oldSaveStyle) {
13494         tm = time((time_t *) NULL);
13495
13496         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13497         PrintOpponents(f);
13498         fprintf(f, "[--------------\n");
13499         PrintPosition(f, currentMove);
13500         fprintf(f, "--------------]\n");
13501     } else {
13502         fen = PositionToFEN(currentMove, NULL, 1);
13503         fprintf(f, "%s\n", fen);
13504         free(fen);
13505     }
13506     fclose(f);
13507     return TRUE;
13508 }
13509
13510 void
13511 ReloadCmailMsgEvent (int unregister)
13512 {
13513 #if !WIN32
13514     static char *inFilename = NULL;
13515     static char *outFilename;
13516     int i;
13517     struct stat inbuf, outbuf;
13518     int status;
13519
13520     /* Any registered moves are unregistered if unregister is set, */
13521     /* i.e. invoked by the signal handler */
13522     if (unregister) {
13523         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13524             cmailMoveRegistered[i] = FALSE;
13525             if (cmailCommentList[i] != NULL) {
13526                 free(cmailCommentList[i]);
13527                 cmailCommentList[i] = NULL;
13528             }
13529         }
13530         nCmailMovesRegistered = 0;
13531     }
13532
13533     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13534         cmailResult[i] = CMAIL_NOT_RESULT;
13535     }
13536     nCmailResults = 0;
13537
13538     if (inFilename == NULL) {
13539         /* Because the filenames are static they only get malloced once  */
13540         /* and they never get freed                                      */
13541         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13542         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13543
13544         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13545         sprintf(outFilename, "%s.out", appData.cmailGameName);
13546     }
13547
13548     status = stat(outFilename, &outbuf);
13549     if (status < 0) {
13550         cmailMailedMove = FALSE;
13551     } else {
13552         status = stat(inFilename, &inbuf);
13553         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13554     }
13555
13556     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13557        counts the games, notes how each one terminated, etc.
13558
13559        It would be nice to remove this kludge and instead gather all
13560        the information while building the game list.  (And to keep it
13561        in the game list nodes instead of having a bunch of fixed-size
13562        parallel arrays.)  Note this will require getting each game's
13563        termination from the PGN tags, as the game list builder does
13564        not process the game moves.  --mann
13565        */
13566     cmailMsgLoaded = TRUE;
13567     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13568
13569     /* Load first game in the file or popup game menu */
13570     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13571
13572 #endif /* !WIN32 */
13573     return;
13574 }
13575
13576 int
13577 RegisterMove ()
13578 {
13579     FILE *f;
13580     char string[MSG_SIZ];
13581
13582     if (   cmailMailedMove
13583         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13584         return TRUE;            /* Allow free viewing  */
13585     }
13586
13587     /* Unregister move to ensure that we don't leave RegisterMove        */
13588     /* with the move registered when the conditions for registering no   */
13589     /* longer hold                                                       */
13590     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13591         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13592         nCmailMovesRegistered --;
13593
13594         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13595           {
13596               free(cmailCommentList[lastLoadGameNumber - 1]);
13597               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13598           }
13599     }
13600
13601     if (cmailOldMove == -1) {
13602         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13603         return FALSE;
13604     }
13605
13606     if (currentMove > cmailOldMove + 1) {
13607         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13608         return FALSE;
13609     }
13610
13611     if (currentMove < cmailOldMove) {
13612         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13613         return FALSE;
13614     }
13615
13616     if (forwardMostMove > currentMove) {
13617         /* Silently truncate extra moves */
13618         TruncateGame();
13619     }
13620
13621     if (   (currentMove == cmailOldMove + 1)
13622         || (   (currentMove == cmailOldMove)
13623             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13624                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13625         if (gameInfo.result != GameUnfinished) {
13626             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13627         }
13628
13629         if (commentList[currentMove] != NULL) {
13630             cmailCommentList[lastLoadGameNumber - 1]
13631               = StrSave(commentList[currentMove]);
13632         }
13633         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13634
13635         if (appData.debugMode)
13636           fprintf(debugFP, "Saving %s for game %d\n",
13637                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13638
13639         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13640
13641         f = fopen(string, "w");
13642         if (appData.oldSaveStyle) {
13643             SaveGameOldStyle(f); /* also closes the file */
13644
13645             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13646             f = fopen(string, "w");
13647             SavePosition(f, 0, NULL); /* also closes the file */
13648         } else {
13649             fprintf(f, "{--------------\n");
13650             PrintPosition(f, currentMove);
13651             fprintf(f, "--------------}\n\n");
13652
13653             SaveGame(f, 0, NULL); /* also closes the file*/
13654         }
13655
13656         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13657         nCmailMovesRegistered ++;
13658     } else if (nCmailGames == 1) {
13659         DisplayError(_("You have not made a move yet"), 0);
13660         return FALSE;
13661     }
13662
13663     return TRUE;
13664 }
13665
13666 void
13667 MailMoveEvent ()
13668 {
13669 #if !WIN32
13670     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13671     FILE *commandOutput;
13672     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13673     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13674     int nBuffers;
13675     int i;
13676     int archived;
13677     char *arcDir;
13678
13679     if (! cmailMsgLoaded) {
13680         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13681         return;
13682     }
13683
13684     if (nCmailGames == nCmailResults) {
13685         DisplayError(_("No unfinished games"), 0);
13686         return;
13687     }
13688
13689 #if CMAIL_PROHIBIT_REMAIL
13690     if (cmailMailedMove) {
13691       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);
13692         DisplayError(msg, 0);
13693         return;
13694     }
13695 #endif
13696
13697     if (! (cmailMailedMove || RegisterMove())) return;
13698
13699     if (   cmailMailedMove
13700         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13701       snprintf(string, MSG_SIZ, partCommandString,
13702                appData.debugMode ? " -v" : "", appData.cmailGameName);
13703         commandOutput = popen(string, "r");
13704
13705         if (commandOutput == NULL) {
13706             DisplayError(_("Failed to invoke cmail"), 0);
13707         } else {
13708             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13709                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13710             }
13711             if (nBuffers > 1) {
13712                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13713                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13714                 nBytes = MSG_SIZ - 1;
13715             } else {
13716                 (void) memcpy(msg, buffer, nBytes);
13717             }
13718             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13719
13720             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13721                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13722
13723                 archived = TRUE;
13724                 for (i = 0; i < nCmailGames; i ++) {
13725                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13726                         archived = FALSE;
13727                     }
13728                 }
13729                 if (   archived
13730                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13731                         != NULL)) {
13732                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13733                            arcDir,
13734                            appData.cmailGameName,
13735                            gameInfo.date);
13736                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13737                     cmailMsgLoaded = FALSE;
13738                 }
13739             }
13740
13741             DisplayInformation(msg);
13742             pclose(commandOutput);
13743         }
13744     } else {
13745         if ((*cmailMsg) != '\0') {
13746             DisplayInformation(cmailMsg);
13747         }
13748     }
13749
13750     return;
13751 #endif /* !WIN32 */
13752 }
13753
13754 char *
13755 CmailMsg ()
13756 {
13757 #if WIN32
13758     return NULL;
13759 #else
13760     int  prependComma = 0;
13761     char number[5];
13762     char string[MSG_SIZ];       /* Space for game-list */
13763     int  i;
13764
13765     if (!cmailMsgLoaded) return "";
13766
13767     if (cmailMailedMove) {
13768       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13769     } else {
13770         /* Create a list of games left */
13771       snprintf(string, MSG_SIZ, "[");
13772         for (i = 0; i < nCmailGames; i ++) {
13773             if (! (   cmailMoveRegistered[i]
13774                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13775                 if (prependComma) {
13776                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13777                 } else {
13778                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13779                     prependComma = 1;
13780                 }
13781
13782                 strcat(string, number);
13783             }
13784         }
13785         strcat(string, "]");
13786
13787         if (nCmailMovesRegistered + nCmailResults == 0) {
13788             switch (nCmailGames) {
13789               case 1:
13790                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13791                 break;
13792
13793               case 2:
13794                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13795                 break;
13796
13797               default:
13798                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13799                          nCmailGames);
13800                 break;
13801             }
13802         } else {
13803             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13804               case 1:
13805                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13806                          string);
13807                 break;
13808
13809               case 0:
13810                 if (nCmailResults == nCmailGames) {
13811                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13812                 } else {
13813                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13814                 }
13815                 break;
13816
13817               default:
13818                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13819                          string);
13820             }
13821         }
13822     }
13823     return cmailMsg;
13824 #endif /* WIN32 */
13825 }
13826
13827 void
13828 ResetGameEvent ()
13829 {
13830     if (gameMode == Training)
13831       SetTrainingModeOff();
13832
13833     Reset(TRUE, TRUE);
13834     cmailMsgLoaded = FALSE;
13835     if (appData.icsActive) {
13836       SendToICS(ics_prefix);
13837       SendToICS("refresh\n");
13838     }
13839 }
13840
13841 void
13842 ExitEvent (int status)
13843 {
13844     exiting++;
13845     if (exiting > 2) {
13846       /* Give up on clean exit */
13847       exit(status);
13848     }
13849     if (exiting > 1) {
13850       /* Keep trying for clean exit */
13851       return;
13852     }
13853
13854     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13855
13856     if (telnetISR != NULL) {
13857       RemoveInputSource(telnetISR);
13858     }
13859     if (icsPR != NoProc) {
13860       DestroyChildProcess(icsPR, TRUE);
13861     }
13862
13863     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13864     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13865
13866     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13867     /* make sure this other one finishes before killing it!                  */
13868     if(endingGame) { int count = 0;
13869         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13870         while(endingGame && count++ < 10) DoSleep(1);
13871         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13872     }
13873
13874     /* Kill off chess programs */
13875     if (first.pr != NoProc) {
13876         ExitAnalyzeMode();
13877
13878         DoSleep( appData.delayBeforeQuit );
13879         SendToProgram("quit\n", &first);
13880         DoSleep( appData.delayAfterQuit );
13881         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13882     }
13883     if (second.pr != NoProc) {
13884         DoSleep( appData.delayBeforeQuit );
13885         SendToProgram("quit\n", &second);
13886         DoSleep( appData.delayAfterQuit );
13887         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13888     }
13889     if (first.isr != NULL) {
13890         RemoveInputSource(first.isr);
13891     }
13892     if (second.isr != NULL) {
13893         RemoveInputSource(second.isr);
13894     }
13895
13896     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13897     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13898
13899     ShutDownFrontEnd();
13900     exit(status);
13901 }
13902
13903 void
13904 PauseEngine (ChessProgramState *cps)
13905 {
13906     SendToProgram("pause\n", cps);
13907     cps->pause = 2;
13908 }
13909
13910 void
13911 UnPauseEngine (ChessProgramState *cps)
13912 {
13913     SendToProgram("resume\n", cps);
13914     cps->pause = 1;
13915 }
13916
13917 void
13918 PauseEvent ()
13919 {
13920     if (appData.debugMode)
13921         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13922     if (pausing) {
13923         pausing = FALSE;
13924         ModeHighlight();
13925         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13926             StartClocks();
13927             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13928                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13929                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13930             }
13931             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13932             HandleMachineMove(stashedInputMove, stalledEngine);
13933             stalledEngine = NULL;
13934             return;
13935         }
13936         if (gameMode == MachinePlaysWhite ||
13937             gameMode == TwoMachinesPlay   ||
13938             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13939             if(first.pause)  UnPauseEngine(&first);
13940             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13941             if(second.pause) UnPauseEngine(&second);
13942             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13943             StartClocks();
13944         } else {
13945             DisplayBothClocks();
13946         }
13947         if (gameMode == PlayFromGameFile) {
13948             if (appData.timeDelay >= 0)
13949                 AutoPlayGameLoop();
13950         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13951             Reset(FALSE, TRUE);
13952             SendToICS(ics_prefix);
13953             SendToICS("refresh\n");
13954         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13955             ForwardInner(forwardMostMove);
13956         }
13957         pauseExamInvalid = FALSE;
13958     } else {
13959         switch (gameMode) {
13960           default:
13961             return;
13962           case IcsExamining:
13963             pauseExamForwardMostMove = forwardMostMove;
13964             pauseExamInvalid = FALSE;
13965             /* fall through */
13966           case IcsObserving:
13967           case IcsPlayingWhite:
13968           case IcsPlayingBlack:
13969             pausing = TRUE;
13970             ModeHighlight();
13971             return;
13972           case PlayFromGameFile:
13973             (void) StopLoadGameTimer();
13974             pausing = TRUE;
13975             ModeHighlight();
13976             break;
13977           case BeginningOfGame:
13978             if (appData.icsActive) return;
13979             /* else fall through */
13980           case MachinePlaysWhite:
13981           case MachinePlaysBlack:
13982           case TwoMachinesPlay:
13983             if (forwardMostMove == 0)
13984               return;           /* don't pause if no one has moved */
13985             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13986                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13987                 if(onMove->pause) {           // thinking engine can be paused
13988                     PauseEngine(onMove);      // do it
13989                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13990                         PauseEngine(onMove->other);
13991                     else
13992                         SendToProgram("easy\n", onMove->other);
13993                     StopClocks();
13994                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13995             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13996                 if(first.pause) {
13997                     PauseEngine(&first);
13998                     StopClocks();
13999                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14000             } else { // human on move, pause pondering by either method
14001                 if(first.pause)
14002                     PauseEngine(&first);
14003                 else if(appData.ponderNextMove)
14004                     SendToProgram("easy\n", &first);
14005                 StopClocks();
14006             }
14007             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14008           case AnalyzeMode:
14009             pausing = TRUE;
14010             ModeHighlight();
14011             break;
14012         }
14013     }
14014 }
14015
14016 void
14017 EditCommentEvent ()
14018 {
14019     char title[MSG_SIZ];
14020
14021     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14022       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14023     } else {
14024       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14025                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14026                parseList[currentMove - 1]);
14027     }
14028
14029     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14030 }
14031
14032
14033 void
14034 EditTagsEvent ()
14035 {
14036     char *tags = PGNTags(&gameInfo);
14037     bookUp = FALSE;
14038     EditTagsPopUp(tags, NULL);
14039     free(tags);
14040 }
14041
14042 void
14043 ToggleSecond ()
14044 {
14045   if(second.analyzing) {
14046     SendToProgram("exit\n", &second);
14047     second.analyzing = FALSE;
14048   } else {
14049     if (second.pr == NoProc) StartChessProgram(&second);
14050     InitChessProgram(&second, FALSE);
14051     FeedMovesToProgram(&second, currentMove);
14052
14053     SendToProgram("analyze\n", &second);
14054     second.analyzing = TRUE;
14055   }
14056 }
14057
14058 /* Toggle ShowThinking */
14059 void
14060 ToggleShowThinking()
14061 {
14062   appData.showThinking = !appData.showThinking;
14063   ShowThinkingEvent();
14064 }
14065
14066 int
14067 AnalyzeModeEvent ()
14068 {
14069     char buf[MSG_SIZ];
14070
14071     if (!first.analysisSupport) {
14072       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14073       DisplayError(buf, 0);
14074       return 0;
14075     }
14076     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14077     if (appData.icsActive) {
14078         if (gameMode != IcsObserving) {
14079           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14080             DisplayError(buf, 0);
14081             /* secure check */
14082             if (appData.icsEngineAnalyze) {
14083                 if (appData.debugMode)
14084                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14085                 ExitAnalyzeMode();
14086                 ModeHighlight();
14087             }
14088             return 0;
14089         }
14090         /* if enable, user wants to disable icsEngineAnalyze */
14091         if (appData.icsEngineAnalyze) {
14092                 ExitAnalyzeMode();
14093                 ModeHighlight();
14094                 return 0;
14095         }
14096         appData.icsEngineAnalyze = TRUE;
14097         if (appData.debugMode)
14098             fprintf(debugFP, "ICS engine analyze starting... \n");
14099     }
14100
14101     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14102     if (appData.noChessProgram || gameMode == AnalyzeMode)
14103       return 0;
14104
14105     if (gameMode != AnalyzeFile) {
14106         if (!appData.icsEngineAnalyze) {
14107                EditGameEvent();
14108                if (gameMode != EditGame) return 0;
14109         }
14110         if (!appData.showThinking) ToggleShowThinking();
14111         ResurrectChessProgram();
14112         SendToProgram("analyze\n", &first);
14113         first.analyzing = TRUE;
14114         /*first.maybeThinking = TRUE;*/
14115         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14116         EngineOutputPopUp();
14117     }
14118     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14119     pausing = FALSE;
14120     ModeHighlight();
14121     SetGameInfo();
14122
14123     StartAnalysisClock();
14124     GetTimeMark(&lastNodeCountTime);
14125     lastNodeCount = 0;
14126     return 1;
14127 }
14128
14129 void
14130 AnalyzeFileEvent ()
14131 {
14132     if (appData.noChessProgram || gameMode == AnalyzeFile)
14133       return;
14134
14135     if (!first.analysisSupport) {
14136       char buf[MSG_SIZ];
14137       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14138       DisplayError(buf, 0);
14139       return;
14140     }
14141
14142     if (gameMode != AnalyzeMode) {
14143         keepInfo = 1; // mere annotating should not alter PGN tags
14144         EditGameEvent();
14145         keepInfo = 0;
14146         if (gameMode != EditGame) return;
14147         if (!appData.showThinking) ToggleShowThinking();
14148         ResurrectChessProgram();
14149         SendToProgram("analyze\n", &first);
14150         first.analyzing = TRUE;
14151         /*first.maybeThinking = TRUE;*/
14152         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14153         EngineOutputPopUp();
14154     }
14155     gameMode = AnalyzeFile;
14156     pausing = FALSE;
14157     ModeHighlight();
14158
14159     StartAnalysisClock();
14160     GetTimeMark(&lastNodeCountTime);
14161     lastNodeCount = 0;
14162     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14163     AnalysisPeriodicEvent(1);
14164 }
14165
14166 void
14167 MachineWhiteEvent ()
14168 {
14169     char buf[MSG_SIZ];
14170     char *bookHit = NULL;
14171
14172     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14173       return;
14174
14175
14176     if (gameMode == PlayFromGameFile ||
14177         gameMode == TwoMachinesPlay  ||
14178         gameMode == Training         ||
14179         gameMode == AnalyzeMode      ||
14180         gameMode == EndOfGame)
14181         EditGameEvent();
14182
14183     if (gameMode == EditPosition)
14184         EditPositionDone(TRUE);
14185
14186     if (!WhiteOnMove(currentMove)) {
14187         DisplayError(_("It is not White's turn"), 0);
14188         return;
14189     }
14190
14191     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14192       ExitAnalyzeMode();
14193
14194     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14195         gameMode == AnalyzeFile)
14196         TruncateGame();
14197
14198     ResurrectChessProgram();    /* in case it isn't running */
14199     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14200         gameMode = MachinePlaysWhite;
14201         ResetClocks();
14202     } else
14203     gameMode = MachinePlaysWhite;
14204     pausing = FALSE;
14205     ModeHighlight();
14206     SetGameInfo();
14207     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14208     DisplayTitle(buf);
14209     if (first.sendName) {
14210       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14211       SendToProgram(buf, &first);
14212     }
14213     if (first.sendTime) {
14214       if (first.useColors) {
14215         SendToProgram("black\n", &first); /*gnu kludge*/
14216       }
14217       SendTimeRemaining(&first, TRUE);
14218     }
14219     if (first.useColors) {
14220       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14221     }
14222     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14223     SetMachineThinkingEnables();
14224     first.maybeThinking = TRUE;
14225     StartClocks();
14226     firstMove = FALSE;
14227
14228     if (appData.autoFlipView && !flipView) {
14229       flipView = !flipView;
14230       DrawPosition(FALSE, NULL);
14231       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14232     }
14233
14234     if(bookHit) { // [HGM] book: simulate book reply
14235         static char bookMove[MSG_SIZ]; // a bit generous?
14236
14237         programStats.nodes = programStats.depth = programStats.time =
14238         programStats.score = programStats.got_only_move = 0;
14239         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14240
14241         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14242         strcat(bookMove, bookHit);
14243         HandleMachineMove(bookMove, &first);
14244     }
14245 }
14246
14247 void
14248 MachineBlackEvent ()
14249 {
14250   char buf[MSG_SIZ];
14251   char *bookHit = NULL;
14252
14253     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14254         return;
14255
14256
14257     if (gameMode == PlayFromGameFile ||
14258         gameMode == TwoMachinesPlay  ||
14259         gameMode == Training         ||
14260         gameMode == AnalyzeMode      ||
14261         gameMode == EndOfGame)
14262         EditGameEvent();
14263
14264     if (gameMode == EditPosition)
14265         EditPositionDone(TRUE);
14266
14267     if (WhiteOnMove(currentMove)) {
14268         DisplayError(_("It is not Black's turn"), 0);
14269         return;
14270     }
14271
14272     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14273       ExitAnalyzeMode();
14274
14275     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14276         gameMode == AnalyzeFile)
14277         TruncateGame();
14278
14279     ResurrectChessProgram();    /* in case it isn't running */
14280     gameMode = MachinePlaysBlack;
14281     pausing = FALSE;
14282     ModeHighlight();
14283     SetGameInfo();
14284     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14285     DisplayTitle(buf);
14286     if (first.sendName) {
14287       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14288       SendToProgram(buf, &first);
14289     }
14290     if (first.sendTime) {
14291       if (first.useColors) {
14292         SendToProgram("white\n", &first); /*gnu kludge*/
14293       }
14294       SendTimeRemaining(&first, FALSE);
14295     }
14296     if (first.useColors) {
14297       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14298     }
14299     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14300     SetMachineThinkingEnables();
14301     first.maybeThinking = TRUE;
14302     StartClocks();
14303
14304     if (appData.autoFlipView && flipView) {
14305       flipView = !flipView;
14306       DrawPosition(FALSE, NULL);
14307       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14308     }
14309     if(bookHit) { // [HGM] book: simulate book reply
14310         static char bookMove[MSG_SIZ]; // a bit generous?
14311
14312         programStats.nodes = programStats.depth = programStats.time =
14313         programStats.score = programStats.got_only_move = 0;
14314         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14315
14316         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14317         strcat(bookMove, bookHit);
14318         HandleMachineMove(bookMove, &first);
14319     }
14320 }
14321
14322
14323 void
14324 DisplayTwoMachinesTitle ()
14325 {
14326     char buf[MSG_SIZ];
14327     if (appData.matchGames > 0) {
14328         if(appData.tourneyFile[0]) {
14329           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14330                    gameInfo.white, _("vs."), gameInfo.black,
14331                    nextGame+1, appData.matchGames+1,
14332                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14333         } else
14334         if (first.twoMachinesColor[0] == 'w') {
14335           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14336                    gameInfo.white, _("vs."),  gameInfo.black,
14337                    first.matchWins, second.matchWins,
14338                    matchGame - 1 - (first.matchWins + second.matchWins));
14339         } else {
14340           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14341                    gameInfo.white, _("vs."), gameInfo.black,
14342                    second.matchWins, first.matchWins,
14343                    matchGame - 1 - (first.matchWins + second.matchWins));
14344         }
14345     } else {
14346       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14347     }
14348     DisplayTitle(buf);
14349 }
14350
14351 void
14352 SettingsMenuIfReady ()
14353 {
14354   if (second.lastPing != second.lastPong) {
14355     DisplayMessage("", _("Waiting for second chess program"));
14356     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14357     return;
14358   }
14359   ThawUI();
14360   DisplayMessage("", "");
14361   SettingsPopUp(&second);
14362 }
14363
14364 int
14365 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14366 {
14367     char buf[MSG_SIZ];
14368     if (cps->pr == NoProc) {
14369         StartChessProgram(cps);
14370         if (cps->protocolVersion == 1) {
14371           retry();
14372           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14373         } else {
14374           /* kludge: allow timeout for initial "feature" command */
14375           if(retry != TwoMachinesEventIfReady) FreezeUI();
14376           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14377           DisplayMessage("", buf);
14378           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14379         }
14380         return 1;
14381     }
14382     return 0;
14383 }
14384
14385 void
14386 TwoMachinesEvent P((void))
14387 {
14388     int i;
14389     char buf[MSG_SIZ];
14390     ChessProgramState *onmove;
14391     char *bookHit = NULL;
14392     static int stalling = 0;
14393     TimeMark now;
14394     long wait;
14395
14396     if (appData.noChessProgram) return;
14397
14398     switch (gameMode) {
14399       case TwoMachinesPlay:
14400         return;
14401       case MachinePlaysWhite:
14402       case MachinePlaysBlack:
14403         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14404             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14405             return;
14406         }
14407         /* fall through */
14408       case BeginningOfGame:
14409       case PlayFromGameFile:
14410       case EndOfGame:
14411         EditGameEvent();
14412         if (gameMode != EditGame) return;
14413         break;
14414       case EditPosition:
14415         EditPositionDone(TRUE);
14416         break;
14417       case AnalyzeMode:
14418       case AnalyzeFile:
14419         ExitAnalyzeMode();
14420         break;
14421       case EditGame:
14422       default:
14423         break;
14424     }
14425
14426 //    forwardMostMove = currentMove;
14427     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14428     startingEngine = TRUE;
14429
14430     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14431
14432     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14433     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14434       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14435       return;
14436     }
14437     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14438
14439     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14440                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14441         startingEngine = FALSE;
14442         DisplayError("second engine does not play this", 0);
14443         return;
14444     }
14445
14446     if(!stalling) {
14447       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14448       SendToProgram("force\n", &second);
14449       stalling = 1;
14450       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14451       return;
14452     }
14453     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14454     if(appData.matchPause>10000 || appData.matchPause<10)
14455                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14456     wait = SubtractTimeMarks(&now, &pauseStart);
14457     if(wait < appData.matchPause) {
14458         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14459         return;
14460     }
14461     // we are now committed to starting the game
14462     stalling = 0;
14463     DisplayMessage("", "");
14464     if (startedFromSetupPosition) {
14465         SendBoard(&second, backwardMostMove);
14466     if (appData.debugMode) {
14467         fprintf(debugFP, "Two Machines\n");
14468     }
14469     }
14470     for (i = backwardMostMove; i < forwardMostMove; i++) {
14471         SendMoveToProgram(i, &second);
14472     }
14473
14474     gameMode = TwoMachinesPlay;
14475     pausing = startingEngine = FALSE;
14476     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14477     SetGameInfo();
14478     DisplayTwoMachinesTitle();
14479     firstMove = TRUE;
14480     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14481         onmove = &first;
14482     } else {
14483         onmove = &second;
14484     }
14485     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14486     SendToProgram(first.computerString, &first);
14487     if (first.sendName) {
14488       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14489       SendToProgram(buf, &first);
14490     }
14491     SendToProgram(second.computerString, &second);
14492     if (second.sendName) {
14493       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14494       SendToProgram(buf, &second);
14495     }
14496
14497     ResetClocks();
14498     if (!first.sendTime || !second.sendTime) {
14499         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14500         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14501     }
14502     if (onmove->sendTime) {
14503       if (onmove->useColors) {
14504         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14505       }
14506       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14507     }
14508     if (onmove->useColors) {
14509       SendToProgram(onmove->twoMachinesColor, onmove);
14510     }
14511     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14512 //    SendToProgram("go\n", onmove);
14513     onmove->maybeThinking = TRUE;
14514     SetMachineThinkingEnables();
14515
14516     StartClocks();
14517
14518     if(bookHit) { // [HGM] book: simulate book reply
14519         static char bookMove[MSG_SIZ]; // a bit generous?
14520
14521         programStats.nodes = programStats.depth = programStats.time =
14522         programStats.score = programStats.got_only_move = 0;
14523         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14524
14525         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14526         strcat(bookMove, bookHit);
14527         savedMessage = bookMove; // args for deferred call
14528         savedState = onmove;
14529         ScheduleDelayedEvent(DeferredBookMove, 1);
14530     }
14531 }
14532
14533 void
14534 TrainingEvent ()
14535 {
14536     if (gameMode == Training) {
14537       SetTrainingModeOff();
14538       gameMode = PlayFromGameFile;
14539       DisplayMessage("", _("Training mode off"));
14540     } else {
14541       gameMode = Training;
14542       animateTraining = appData.animate;
14543
14544       /* make sure we are not already at the end of the game */
14545       if (currentMove < forwardMostMove) {
14546         SetTrainingModeOn();
14547         DisplayMessage("", _("Training mode on"));
14548       } else {
14549         gameMode = PlayFromGameFile;
14550         DisplayError(_("Already at end of game"), 0);
14551       }
14552     }
14553     ModeHighlight();
14554 }
14555
14556 void
14557 IcsClientEvent ()
14558 {
14559     if (!appData.icsActive) return;
14560     switch (gameMode) {
14561       case IcsPlayingWhite:
14562       case IcsPlayingBlack:
14563       case IcsObserving:
14564       case IcsIdle:
14565       case BeginningOfGame:
14566       case IcsExamining:
14567         return;
14568
14569       case EditGame:
14570         break;
14571
14572       case EditPosition:
14573         EditPositionDone(TRUE);
14574         break;
14575
14576       case AnalyzeMode:
14577       case AnalyzeFile:
14578         ExitAnalyzeMode();
14579         break;
14580
14581       default:
14582         EditGameEvent();
14583         break;
14584     }
14585
14586     gameMode = IcsIdle;
14587     ModeHighlight();
14588     return;
14589 }
14590
14591 void
14592 EditGameEvent ()
14593 {
14594     int i;
14595
14596     switch (gameMode) {
14597       case Training:
14598         SetTrainingModeOff();
14599         break;
14600       case MachinePlaysWhite:
14601       case MachinePlaysBlack:
14602       case BeginningOfGame:
14603         SendToProgram("force\n", &first);
14604         SetUserThinkingEnables();
14605         break;
14606       case PlayFromGameFile:
14607         (void) StopLoadGameTimer();
14608         if (gameFileFP != NULL) {
14609             gameFileFP = NULL;
14610         }
14611         break;
14612       case EditPosition:
14613         EditPositionDone(TRUE);
14614         break;
14615       case AnalyzeMode:
14616       case AnalyzeFile:
14617         ExitAnalyzeMode();
14618         SendToProgram("force\n", &first);
14619         break;
14620       case TwoMachinesPlay:
14621         GameEnds(EndOfFile, NULL, GE_PLAYER);
14622         ResurrectChessProgram();
14623         SetUserThinkingEnables();
14624         break;
14625       case EndOfGame:
14626         ResurrectChessProgram();
14627         break;
14628       case IcsPlayingBlack:
14629       case IcsPlayingWhite:
14630         DisplayError(_("Warning: You are still playing a game"), 0);
14631         break;
14632       case IcsObserving:
14633         DisplayError(_("Warning: You are still observing a game"), 0);
14634         break;
14635       case IcsExamining:
14636         DisplayError(_("Warning: You are still examining a game"), 0);
14637         break;
14638       case IcsIdle:
14639         break;
14640       case EditGame:
14641       default:
14642         return;
14643     }
14644
14645     pausing = FALSE;
14646     StopClocks();
14647     first.offeredDraw = second.offeredDraw = 0;
14648
14649     if (gameMode == PlayFromGameFile) {
14650         whiteTimeRemaining = timeRemaining[0][currentMove];
14651         blackTimeRemaining = timeRemaining[1][currentMove];
14652         DisplayTitle("");
14653     }
14654
14655     if (gameMode == MachinePlaysWhite ||
14656         gameMode == MachinePlaysBlack ||
14657         gameMode == TwoMachinesPlay ||
14658         gameMode == EndOfGame) {
14659         i = forwardMostMove;
14660         while (i > currentMove) {
14661             SendToProgram("undo\n", &first);
14662             i--;
14663         }
14664         if(!adjustedClock) {
14665         whiteTimeRemaining = timeRemaining[0][currentMove];
14666         blackTimeRemaining = timeRemaining[1][currentMove];
14667         DisplayBothClocks();
14668         }
14669         if (whiteFlag || blackFlag) {
14670             whiteFlag = blackFlag = 0;
14671         }
14672         DisplayTitle("");
14673     }
14674
14675     gameMode = EditGame;
14676     ModeHighlight();
14677     SetGameInfo();
14678 }
14679
14680
14681 void
14682 EditPositionEvent ()
14683 {
14684     if (gameMode == EditPosition) {
14685         EditGameEvent();
14686         return;
14687     }
14688
14689     EditGameEvent();
14690     if (gameMode != EditGame) return;
14691
14692     gameMode = EditPosition;
14693     ModeHighlight();
14694     SetGameInfo();
14695     if (currentMove > 0)
14696       CopyBoard(boards[0], boards[currentMove]);
14697
14698     blackPlaysFirst = !WhiteOnMove(currentMove);
14699     ResetClocks();
14700     currentMove = forwardMostMove = backwardMostMove = 0;
14701     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14702     DisplayMove(-1);
14703     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14704 }
14705
14706 void
14707 ExitAnalyzeMode ()
14708 {
14709     /* [DM] icsEngineAnalyze - possible call from other functions */
14710     if (appData.icsEngineAnalyze) {
14711         appData.icsEngineAnalyze = FALSE;
14712
14713         DisplayMessage("",_("Close ICS engine analyze..."));
14714     }
14715     if (first.analysisSupport && first.analyzing) {
14716       SendToBoth("exit\n");
14717       first.analyzing = second.analyzing = FALSE;
14718     }
14719     thinkOutput[0] = NULLCHAR;
14720 }
14721
14722 void
14723 EditPositionDone (Boolean fakeRights)
14724 {
14725     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14726
14727     startedFromSetupPosition = TRUE;
14728     InitChessProgram(&first, FALSE);
14729     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14730       boards[0][EP_STATUS] = EP_NONE;
14731       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14732       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14733         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14734         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14735       } else boards[0][CASTLING][2] = NoRights;
14736       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14737         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14738         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14739       } else boards[0][CASTLING][5] = NoRights;
14740       if(gameInfo.variant == VariantSChess) {
14741         int i;
14742         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14743           boards[0][VIRGIN][i] = 0;
14744           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14745           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14746         }
14747       }
14748     }
14749     SendToProgram("force\n", &first);
14750     if (blackPlaysFirst) {
14751         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14752         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14753         currentMove = forwardMostMove = backwardMostMove = 1;
14754         CopyBoard(boards[1], boards[0]);
14755     } else {
14756         currentMove = forwardMostMove = backwardMostMove = 0;
14757     }
14758     SendBoard(&first, forwardMostMove);
14759     if (appData.debugMode) {
14760         fprintf(debugFP, "EditPosDone\n");
14761     }
14762     DisplayTitle("");
14763     DisplayMessage("", "");
14764     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14765     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14766     gameMode = EditGame;
14767     ModeHighlight();
14768     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14769     ClearHighlights(); /* [AS] */
14770 }
14771
14772 /* Pause for `ms' milliseconds */
14773 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14774 void
14775 TimeDelay (long ms)
14776 {
14777     TimeMark m1, m2;
14778
14779     GetTimeMark(&m1);
14780     do {
14781         GetTimeMark(&m2);
14782     } while (SubtractTimeMarks(&m2, &m1) < ms);
14783 }
14784
14785 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14786 void
14787 SendMultiLineToICS (char *buf)
14788 {
14789     char temp[MSG_SIZ+1], *p;
14790     int len;
14791
14792     len = strlen(buf);
14793     if (len > MSG_SIZ)
14794       len = MSG_SIZ;
14795
14796     strncpy(temp, buf, len);
14797     temp[len] = 0;
14798
14799     p = temp;
14800     while (*p) {
14801         if (*p == '\n' || *p == '\r')
14802           *p = ' ';
14803         ++p;
14804     }
14805
14806     strcat(temp, "\n");
14807     SendToICS(temp);
14808     SendToPlayer(temp, strlen(temp));
14809 }
14810
14811 void
14812 SetWhiteToPlayEvent ()
14813 {
14814     if (gameMode == EditPosition) {
14815         blackPlaysFirst = FALSE;
14816         DisplayBothClocks();    /* works because currentMove is 0 */
14817     } else if (gameMode == IcsExamining) {
14818         SendToICS(ics_prefix);
14819         SendToICS("tomove white\n");
14820     }
14821 }
14822
14823 void
14824 SetBlackToPlayEvent ()
14825 {
14826     if (gameMode == EditPosition) {
14827         blackPlaysFirst = TRUE;
14828         currentMove = 1;        /* kludge */
14829         DisplayBothClocks();
14830         currentMove = 0;
14831     } else if (gameMode == IcsExamining) {
14832         SendToICS(ics_prefix);
14833         SendToICS("tomove black\n");
14834     }
14835 }
14836
14837 void
14838 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14839 {
14840     char buf[MSG_SIZ];
14841     ChessSquare piece = boards[0][y][x];
14842     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14843     static int lastVariant;
14844
14845     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14846
14847     switch (selection) {
14848       case ClearBoard:
14849         CopyBoard(currentBoard, boards[0]);
14850         CopyBoard(menuBoard, initialPosition);
14851         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14852             SendToICS(ics_prefix);
14853             SendToICS("bsetup clear\n");
14854         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14855             SendToICS(ics_prefix);
14856             SendToICS("clearboard\n");
14857         } else {
14858             int nonEmpty = 0;
14859             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14860                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14861                 for (y = 0; y < BOARD_HEIGHT; y++) {
14862                     if (gameMode == IcsExamining) {
14863                         if (boards[currentMove][y][x] != EmptySquare) {
14864                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14865                                     AAA + x, ONE + y);
14866                             SendToICS(buf);
14867                         }
14868                     } else {
14869                         if(boards[0][y][x] != p) nonEmpty++;
14870                         boards[0][y][x] = p;
14871                     }
14872                 }
14873                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14874             }
14875             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14876                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14877                     ChessSquare p = menuBoard[0][x];
14878                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14879                     p = menuBoard[BOARD_HEIGHT-1][x];
14880                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14881                 }
14882                 DisplayMessage("Clicking clock again restores position", "");
14883                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14884                 if(!nonEmpty) { // asked to clear an empty board
14885                     CopyBoard(boards[0], menuBoard);
14886                 } else
14887                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14888                     CopyBoard(boards[0], initialPosition);
14889                 } else
14890                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14891                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14892                     CopyBoard(boards[0], erasedBoard);
14893                 } else
14894                     CopyBoard(erasedBoard, currentBoard);
14895
14896             }
14897         }
14898         if (gameMode == EditPosition) {
14899             DrawPosition(FALSE, boards[0]);
14900         }
14901         break;
14902
14903       case WhitePlay:
14904         SetWhiteToPlayEvent();
14905         break;
14906
14907       case BlackPlay:
14908         SetBlackToPlayEvent();
14909         break;
14910
14911       case EmptySquare:
14912         if (gameMode == IcsExamining) {
14913             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14914             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14915             SendToICS(buf);
14916         } else {
14917             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14918                 if(x == BOARD_LEFT-2) {
14919                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14920                     boards[0][y][1] = 0;
14921                 } else
14922                 if(x == BOARD_RGHT+1) {
14923                     if(y >= gameInfo.holdingsSize) break;
14924                     boards[0][y][BOARD_WIDTH-2] = 0;
14925                 } else break;
14926             }
14927             boards[0][y][x] = EmptySquare;
14928             DrawPosition(FALSE, boards[0]);
14929         }
14930         break;
14931
14932       case PromotePiece:
14933         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14934            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14935             selection = (ChessSquare) (PROMOTED piece);
14936         } else if(piece == EmptySquare) selection = WhiteSilver;
14937         else selection = (ChessSquare)((int)piece - 1);
14938         goto defaultlabel;
14939
14940       case DemotePiece:
14941         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14942            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14943             selection = (ChessSquare) (DEMOTED piece);
14944         } else if(piece == EmptySquare) selection = BlackSilver;
14945         else selection = (ChessSquare)((int)piece + 1);
14946         goto defaultlabel;
14947
14948       case WhiteQueen:
14949       case BlackQueen:
14950         if(gameInfo.variant == VariantShatranj ||
14951            gameInfo.variant == VariantXiangqi  ||
14952            gameInfo.variant == VariantCourier  ||
14953            gameInfo.variant == VariantASEAN    ||
14954            gameInfo.variant == VariantMakruk     )
14955             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14956         goto defaultlabel;
14957
14958       case WhiteKing:
14959       case BlackKing:
14960         if(gameInfo.variant == VariantXiangqi)
14961             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14962         if(gameInfo.variant == VariantKnightmate)
14963             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14964       default:
14965         defaultlabel:
14966         if (gameMode == IcsExamining) {
14967             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14968             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14969                      PieceToChar(selection), AAA + x, ONE + y);
14970             SendToICS(buf);
14971         } else {
14972             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14973                 int n;
14974                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14975                     n = PieceToNumber(selection - BlackPawn);
14976                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14977                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14978                     boards[0][BOARD_HEIGHT-1-n][1]++;
14979                 } else
14980                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14981                     n = PieceToNumber(selection);
14982                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14983                     boards[0][n][BOARD_WIDTH-1] = selection;
14984                     boards[0][n][BOARD_WIDTH-2]++;
14985                 }
14986             } else
14987             boards[0][y][x] = selection;
14988             DrawPosition(TRUE, boards[0]);
14989             ClearHighlights();
14990             fromX = fromY = -1;
14991         }
14992         break;
14993     }
14994 }
14995
14996
14997 void
14998 DropMenuEvent (ChessSquare selection, int x, int y)
14999 {
15000     ChessMove moveType;
15001
15002     switch (gameMode) {
15003       case IcsPlayingWhite:
15004       case MachinePlaysBlack:
15005         if (!WhiteOnMove(currentMove)) {
15006             DisplayMoveError(_("It is Black's turn"));
15007             return;
15008         }
15009         moveType = WhiteDrop;
15010         break;
15011       case IcsPlayingBlack:
15012       case MachinePlaysWhite:
15013         if (WhiteOnMove(currentMove)) {
15014             DisplayMoveError(_("It is White's turn"));
15015             return;
15016         }
15017         moveType = BlackDrop;
15018         break;
15019       case EditGame:
15020         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15021         break;
15022       default:
15023         return;
15024     }
15025
15026     if (moveType == BlackDrop && selection < BlackPawn) {
15027       selection = (ChessSquare) ((int) selection
15028                                  + (int) BlackPawn - (int) WhitePawn);
15029     }
15030     if (boards[currentMove][y][x] != EmptySquare) {
15031         DisplayMoveError(_("That square is occupied"));
15032         return;
15033     }
15034
15035     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15036 }
15037
15038 void
15039 AcceptEvent ()
15040 {
15041     /* Accept a pending offer of any kind from opponent */
15042
15043     if (appData.icsActive) {
15044         SendToICS(ics_prefix);
15045         SendToICS("accept\n");
15046     } else if (cmailMsgLoaded) {
15047         if (currentMove == cmailOldMove &&
15048             commentList[cmailOldMove] != NULL &&
15049             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15050                    "Black offers a draw" : "White offers a draw")) {
15051             TruncateGame();
15052             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15053             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15054         } else {
15055             DisplayError(_("There is no pending offer on this move"), 0);
15056             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15057         }
15058     } else {
15059         /* Not used for offers from chess program */
15060     }
15061 }
15062
15063 void
15064 DeclineEvent ()
15065 {
15066     /* Decline a pending offer of any kind from opponent */
15067
15068     if (appData.icsActive) {
15069         SendToICS(ics_prefix);
15070         SendToICS("decline\n");
15071     } else if (cmailMsgLoaded) {
15072         if (currentMove == cmailOldMove &&
15073             commentList[cmailOldMove] != NULL &&
15074             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15075                    "Black offers a draw" : "White offers a draw")) {
15076 #ifdef NOTDEF
15077             AppendComment(cmailOldMove, "Draw declined", TRUE);
15078             DisplayComment(cmailOldMove - 1, "Draw declined");
15079 #endif /*NOTDEF*/
15080         } else {
15081             DisplayError(_("There is no pending offer on this move"), 0);
15082         }
15083     } else {
15084         /* Not used for offers from chess program */
15085     }
15086 }
15087
15088 void
15089 RematchEvent ()
15090 {
15091     /* Issue ICS rematch command */
15092     if (appData.icsActive) {
15093         SendToICS(ics_prefix);
15094         SendToICS("rematch\n");
15095     }
15096 }
15097
15098 void
15099 CallFlagEvent ()
15100 {
15101     /* Call your opponent's flag (claim a win on time) */
15102     if (appData.icsActive) {
15103         SendToICS(ics_prefix);
15104         SendToICS("flag\n");
15105     } else {
15106         switch (gameMode) {
15107           default:
15108             return;
15109           case MachinePlaysWhite:
15110             if (whiteFlag) {
15111                 if (blackFlag)
15112                   GameEnds(GameIsDrawn, "Both players ran out of time",
15113                            GE_PLAYER);
15114                 else
15115                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15116             } else {
15117                 DisplayError(_("Your opponent is not out of time"), 0);
15118             }
15119             break;
15120           case MachinePlaysBlack:
15121             if (blackFlag) {
15122                 if (whiteFlag)
15123                   GameEnds(GameIsDrawn, "Both players ran out of time",
15124                            GE_PLAYER);
15125                 else
15126                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15127             } else {
15128                 DisplayError(_("Your opponent is not out of time"), 0);
15129             }
15130             break;
15131         }
15132     }
15133 }
15134
15135 void
15136 ClockClick (int which)
15137 {       // [HGM] code moved to back-end from winboard.c
15138         if(which) { // black clock
15139           if (gameMode == EditPosition || gameMode == IcsExamining) {
15140             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15141             SetBlackToPlayEvent();
15142           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15143           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15144           } else if (shiftKey) {
15145             AdjustClock(which, -1);
15146           } else if (gameMode == IcsPlayingWhite ||
15147                      gameMode == MachinePlaysBlack) {
15148             CallFlagEvent();
15149           }
15150         } else { // white clock
15151           if (gameMode == EditPosition || gameMode == IcsExamining) {
15152             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15153             SetWhiteToPlayEvent();
15154           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15155           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15156           } else if (shiftKey) {
15157             AdjustClock(which, -1);
15158           } else if (gameMode == IcsPlayingBlack ||
15159                    gameMode == MachinePlaysWhite) {
15160             CallFlagEvent();
15161           }
15162         }
15163 }
15164
15165 void
15166 DrawEvent ()
15167 {
15168     /* Offer draw or accept pending draw offer from opponent */
15169
15170     if (appData.icsActive) {
15171         /* Note: tournament rules require draw offers to be
15172            made after you make your move but before you punch
15173            your clock.  Currently ICS doesn't let you do that;
15174            instead, you immediately punch your clock after making
15175            a move, but you can offer a draw at any time. */
15176
15177         SendToICS(ics_prefix);
15178         SendToICS("draw\n");
15179         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15180     } else if (cmailMsgLoaded) {
15181         if (currentMove == cmailOldMove &&
15182             commentList[cmailOldMove] != NULL &&
15183             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15184                    "Black offers a draw" : "White offers a draw")) {
15185             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15186             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15187         } else if (currentMove == cmailOldMove + 1) {
15188             char *offer = WhiteOnMove(cmailOldMove) ?
15189               "White offers a draw" : "Black offers a draw";
15190             AppendComment(currentMove, offer, TRUE);
15191             DisplayComment(currentMove - 1, offer);
15192             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15193         } else {
15194             DisplayError(_("You must make your move before offering a draw"), 0);
15195             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15196         }
15197     } else if (first.offeredDraw) {
15198         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15199     } else {
15200         if (first.sendDrawOffers) {
15201             SendToProgram("draw\n", &first);
15202             userOfferedDraw = TRUE;
15203         }
15204     }
15205 }
15206
15207 void
15208 AdjournEvent ()
15209 {
15210     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15211
15212     if (appData.icsActive) {
15213         SendToICS(ics_prefix);
15214         SendToICS("adjourn\n");
15215     } else {
15216         /* Currently GNU Chess doesn't offer or accept Adjourns */
15217     }
15218 }
15219
15220
15221 void
15222 AbortEvent ()
15223 {
15224     /* Offer Abort or accept pending Abort offer from opponent */
15225
15226     if (appData.icsActive) {
15227         SendToICS(ics_prefix);
15228         SendToICS("abort\n");
15229     } else {
15230         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15231     }
15232 }
15233
15234 void
15235 ResignEvent ()
15236 {
15237     /* Resign.  You can do this even if it's not your turn. */
15238
15239     if (appData.icsActive) {
15240         SendToICS(ics_prefix);
15241         SendToICS("resign\n");
15242     } else {
15243         switch (gameMode) {
15244           case MachinePlaysWhite:
15245             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15246             break;
15247           case MachinePlaysBlack:
15248             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15249             break;
15250           case EditGame:
15251             if (cmailMsgLoaded) {
15252                 TruncateGame();
15253                 if (WhiteOnMove(cmailOldMove)) {
15254                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15255                 } else {
15256                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15257                 }
15258                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15259             }
15260             break;
15261           default:
15262             break;
15263         }
15264     }
15265 }
15266
15267
15268 void
15269 StopObservingEvent ()
15270 {
15271     /* Stop observing current games */
15272     SendToICS(ics_prefix);
15273     SendToICS("unobserve\n");
15274 }
15275
15276 void
15277 StopExaminingEvent ()
15278 {
15279     /* Stop observing current game */
15280     SendToICS(ics_prefix);
15281     SendToICS("unexamine\n");
15282 }
15283
15284 void
15285 ForwardInner (int target)
15286 {
15287     int limit; int oldSeekGraphUp = seekGraphUp;
15288
15289     if (appData.debugMode)
15290         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15291                 target, currentMove, forwardMostMove);
15292
15293     if (gameMode == EditPosition)
15294       return;
15295
15296     seekGraphUp = FALSE;
15297     MarkTargetSquares(1);
15298
15299     if (gameMode == PlayFromGameFile && !pausing)
15300       PauseEvent();
15301
15302     if (gameMode == IcsExamining && pausing)
15303       limit = pauseExamForwardMostMove;
15304     else
15305       limit = forwardMostMove;
15306
15307     if (target > limit) target = limit;
15308
15309     if (target > 0 && moveList[target - 1][0]) {
15310         int fromX, fromY, toX, toY;
15311         toX = moveList[target - 1][2] - AAA;
15312         toY = moveList[target - 1][3] - ONE;
15313         if (moveList[target - 1][1] == '@') {
15314             if (appData.highlightLastMove) {
15315                 SetHighlights(-1, -1, toX, toY);
15316             }
15317         } else {
15318             fromX = moveList[target - 1][0] - AAA;
15319             fromY = moveList[target - 1][1] - ONE;
15320             if (target == currentMove + 1) {
15321                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15322             }
15323             if (appData.highlightLastMove) {
15324                 SetHighlights(fromX, fromY, toX, toY);
15325             }
15326         }
15327     }
15328     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15329         gameMode == Training || gameMode == PlayFromGameFile ||
15330         gameMode == AnalyzeFile) {
15331         while (currentMove < target) {
15332             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15333             SendMoveToProgram(currentMove++, &first);
15334         }
15335     } else {
15336         currentMove = target;
15337     }
15338
15339     if (gameMode == EditGame || gameMode == EndOfGame) {
15340         whiteTimeRemaining = timeRemaining[0][currentMove];
15341         blackTimeRemaining = timeRemaining[1][currentMove];
15342     }
15343     DisplayBothClocks();
15344     DisplayMove(currentMove - 1);
15345     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15346     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15347     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15348         DisplayComment(currentMove - 1, commentList[currentMove]);
15349     }
15350     ClearMap(); // [HGM] exclude: invalidate map
15351 }
15352
15353
15354 void
15355 ForwardEvent ()
15356 {
15357     if (gameMode == IcsExamining && !pausing) {
15358         SendToICS(ics_prefix);
15359         SendToICS("forward\n");
15360     } else {
15361         ForwardInner(currentMove + 1);
15362     }
15363 }
15364
15365 void
15366 ToEndEvent ()
15367 {
15368     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15369         /* to optimze, we temporarily turn off analysis mode while we feed
15370          * the remaining moves to the engine. Otherwise we get analysis output
15371          * after each move.
15372          */
15373         if (first.analysisSupport) {
15374           SendToProgram("exit\nforce\n", &first);
15375           first.analyzing = FALSE;
15376         }
15377     }
15378
15379     if (gameMode == IcsExamining && !pausing) {
15380         SendToICS(ics_prefix);
15381         SendToICS("forward 999999\n");
15382     } else {
15383         ForwardInner(forwardMostMove);
15384     }
15385
15386     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15387         /* we have fed all the moves, so reactivate analysis mode */
15388         SendToProgram("analyze\n", &first);
15389         first.analyzing = TRUE;
15390         /*first.maybeThinking = TRUE;*/
15391         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15392     }
15393 }
15394
15395 void
15396 BackwardInner (int target)
15397 {
15398     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15399
15400     if (appData.debugMode)
15401         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15402                 target, currentMove, forwardMostMove);
15403
15404     if (gameMode == EditPosition) return;
15405     seekGraphUp = FALSE;
15406     MarkTargetSquares(1);
15407     if (currentMove <= backwardMostMove) {
15408         ClearHighlights();
15409         DrawPosition(full_redraw, boards[currentMove]);
15410         return;
15411     }
15412     if (gameMode == PlayFromGameFile && !pausing)
15413       PauseEvent();
15414
15415     if (moveList[target][0]) {
15416         int fromX, fromY, toX, toY;
15417         toX = moveList[target][2] - AAA;
15418         toY = moveList[target][3] - ONE;
15419         if (moveList[target][1] == '@') {
15420             if (appData.highlightLastMove) {
15421                 SetHighlights(-1, -1, toX, toY);
15422             }
15423         } else {
15424             fromX = moveList[target][0] - AAA;
15425             fromY = moveList[target][1] - ONE;
15426             if (target == currentMove - 1) {
15427                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15428             }
15429             if (appData.highlightLastMove) {
15430                 SetHighlights(fromX, fromY, toX, toY);
15431             }
15432         }
15433     }
15434     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15435         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15436         while (currentMove > target) {
15437             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15438                 // null move cannot be undone. Reload program with move history before it.
15439                 int i;
15440                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15441                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15442                 }
15443                 SendBoard(&first, i);
15444               if(second.analyzing) SendBoard(&second, i);
15445                 for(currentMove=i; currentMove<target; currentMove++) {
15446                     SendMoveToProgram(currentMove, &first);
15447                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15448                 }
15449                 break;
15450             }
15451             SendToBoth("undo\n");
15452             currentMove--;
15453         }
15454     } else {
15455         currentMove = target;
15456     }
15457
15458     if (gameMode == EditGame || gameMode == EndOfGame) {
15459         whiteTimeRemaining = timeRemaining[0][currentMove];
15460         blackTimeRemaining = timeRemaining[1][currentMove];
15461     }
15462     DisplayBothClocks();
15463     DisplayMove(currentMove - 1);
15464     DrawPosition(full_redraw, boards[currentMove]);
15465     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15466     // [HGM] PV info: routine tests if comment empty
15467     DisplayComment(currentMove - 1, commentList[currentMove]);
15468     ClearMap(); // [HGM] exclude: invalidate map
15469 }
15470
15471 void
15472 BackwardEvent ()
15473 {
15474     if (gameMode == IcsExamining && !pausing) {
15475         SendToICS(ics_prefix);
15476         SendToICS("backward\n");
15477     } else {
15478         BackwardInner(currentMove - 1);
15479     }
15480 }
15481
15482 void
15483 ToStartEvent ()
15484 {
15485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15486         /* to optimize, we temporarily turn off analysis mode while we undo
15487          * all the moves. Otherwise we get analysis output after each undo.
15488          */
15489         if (first.analysisSupport) {
15490           SendToProgram("exit\nforce\n", &first);
15491           first.analyzing = FALSE;
15492         }
15493     }
15494
15495     if (gameMode == IcsExamining && !pausing) {
15496         SendToICS(ics_prefix);
15497         SendToICS("backward 999999\n");
15498     } else {
15499         BackwardInner(backwardMostMove);
15500     }
15501
15502     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15503         /* we have fed all the moves, so reactivate analysis mode */
15504         SendToProgram("analyze\n", &first);
15505         first.analyzing = TRUE;
15506         /*first.maybeThinking = TRUE;*/
15507         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15508     }
15509 }
15510
15511 void
15512 ToNrEvent (int to)
15513 {
15514   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15515   if (to >= forwardMostMove) to = forwardMostMove;
15516   if (to <= backwardMostMove) to = backwardMostMove;
15517   if (to < currentMove) {
15518     BackwardInner(to);
15519   } else {
15520     ForwardInner(to);
15521   }
15522 }
15523
15524 void
15525 RevertEvent (Boolean annotate)
15526 {
15527     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15528         return;
15529     }
15530     if (gameMode != IcsExamining) {
15531         DisplayError(_("You are not examining a game"), 0);
15532         return;
15533     }
15534     if (pausing) {
15535         DisplayError(_("You can't revert while pausing"), 0);
15536         return;
15537     }
15538     SendToICS(ics_prefix);
15539     SendToICS("revert\n");
15540 }
15541
15542 void
15543 RetractMoveEvent ()
15544 {
15545     switch (gameMode) {
15546       case MachinePlaysWhite:
15547       case MachinePlaysBlack:
15548         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15549             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15550             return;
15551         }
15552         if (forwardMostMove < 2) return;
15553         currentMove = forwardMostMove = forwardMostMove - 2;
15554         whiteTimeRemaining = timeRemaining[0][currentMove];
15555         blackTimeRemaining = timeRemaining[1][currentMove];
15556         DisplayBothClocks();
15557         DisplayMove(currentMove - 1);
15558         ClearHighlights();/*!! could figure this out*/
15559         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15560         SendToProgram("remove\n", &first);
15561         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15562         break;
15563
15564       case BeginningOfGame:
15565       default:
15566         break;
15567
15568       case IcsPlayingWhite:
15569       case IcsPlayingBlack:
15570         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15571             SendToICS(ics_prefix);
15572             SendToICS("takeback 2\n");
15573         } else {
15574             SendToICS(ics_prefix);
15575             SendToICS("takeback 1\n");
15576         }
15577         break;
15578     }
15579 }
15580
15581 void
15582 MoveNowEvent ()
15583 {
15584     ChessProgramState *cps;
15585
15586     switch (gameMode) {
15587       case MachinePlaysWhite:
15588         if (!WhiteOnMove(forwardMostMove)) {
15589             DisplayError(_("It is your turn"), 0);
15590             return;
15591         }
15592         cps = &first;
15593         break;
15594       case MachinePlaysBlack:
15595         if (WhiteOnMove(forwardMostMove)) {
15596             DisplayError(_("It is your turn"), 0);
15597             return;
15598         }
15599         cps = &first;
15600         break;
15601       case TwoMachinesPlay:
15602         if (WhiteOnMove(forwardMostMove) ==
15603             (first.twoMachinesColor[0] == 'w')) {
15604             cps = &first;
15605         } else {
15606             cps = &second;
15607         }
15608         break;
15609       case BeginningOfGame:
15610       default:
15611         return;
15612     }
15613     SendToProgram("?\n", cps);
15614 }
15615
15616 void
15617 TruncateGameEvent ()
15618 {
15619     EditGameEvent();
15620     if (gameMode != EditGame) return;
15621     TruncateGame();
15622 }
15623
15624 void
15625 TruncateGame ()
15626 {
15627     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15628     if (forwardMostMove > currentMove) {
15629         if (gameInfo.resultDetails != NULL) {
15630             free(gameInfo.resultDetails);
15631             gameInfo.resultDetails = NULL;
15632             gameInfo.result = GameUnfinished;
15633         }
15634         forwardMostMove = currentMove;
15635         HistorySet(parseList, backwardMostMove, forwardMostMove,
15636                    currentMove-1);
15637     }
15638 }
15639
15640 void
15641 HintEvent ()
15642 {
15643     if (appData.noChessProgram) return;
15644     switch (gameMode) {
15645       case MachinePlaysWhite:
15646         if (WhiteOnMove(forwardMostMove)) {
15647             DisplayError(_("Wait until your turn."), 0);
15648             return;
15649         }
15650         break;
15651       case BeginningOfGame:
15652       case MachinePlaysBlack:
15653         if (!WhiteOnMove(forwardMostMove)) {
15654             DisplayError(_("Wait until your turn."), 0);
15655             return;
15656         }
15657         break;
15658       default:
15659         DisplayError(_("No hint available"), 0);
15660         return;
15661     }
15662     SendToProgram("hint\n", &first);
15663     hintRequested = TRUE;
15664 }
15665
15666 void
15667 CreateBookEvent ()
15668 {
15669     ListGame * lg = (ListGame *) gameList.head;
15670     FILE *f, *g;
15671     int nItem;
15672     static int secondTime = FALSE;
15673
15674     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15675         DisplayError(_("Game list not loaded or empty"), 0);
15676         return;
15677     }
15678
15679     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15680         fclose(g);
15681         secondTime++;
15682         DisplayNote(_("Book file exists! Try again for overwrite."));
15683         return;
15684     }
15685
15686     creatingBook = TRUE;
15687     secondTime = FALSE;
15688
15689     /* Get list size */
15690     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15691         LoadGame(f, nItem, "", TRUE);
15692         AddGameToBook(TRUE);
15693         lg = (ListGame *) lg->node.succ;
15694     }
15695
15696     creatingBook = FALSE;
15697     FlushBook();
15698 }
15699
15700 void
15701 BookEvent ()
15702 {
15703     if (appData.noChessProgram) return;
15704     switch (gameMode) {
15705       case MachinePlaysWhite:
15706         if (WhiteOnMove(forwardMostMove)) {
15707             DisplayError(_("Wait until your turn."), 0);
15708             return;
15709         }
15710         break;
15711       case BeginningOfGame:
15712       case MachinePlaysBlack:
15713         if (!WhiteOnMove(forwardMostMove)) {
15714             DisplayError(_("Wait until your turn."), 0);
15715             return;
15716         }
15717         break;
15718       case EditPosition:
15719         EditPositionDone(TRUE);
15720         break;
15721       case TwoMachinesPlay:
15722         return;
15723       default:
15724         break;
15725     }
15726     SendToProgram("bk\n", &first);
15727     bookOutput[0] = NULLCHAR;
15728     bookRequested = TRUE;
15729 }
15730
15731 void
15732 AboutGameEvent ()
15733 {
15734     char *tags = PGNTags(&gameInfo);
15735     TagsPopUp(tags, CmailMsg());
15736     free(tags);
15737 }
15738
15739 /* end button procedures */
15740
15741 void
15742 PrintPosition (FILE *fp, int move)
15743 {
15744     int i, j;
15745
15746     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15747         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15748             char c = PieceToChar(boards[move][i][j]);
15749             fputc(c == 'x' ? '.' : c, fp);
15750             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15751         }
15752     }
15753     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15754       fprintf(fp, "white to play\n");
15755     else
15756       fprintf(fp, "black to play\n");
15757 }
15758
15759 void
15760 PrintOpponents (FILE *fp)
15761 {
15762     if (gameInfo.white != NULL) {
15763         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15764     } else {
15765         fprintf(fp, "\n");
15766     }
15767 }
15768
15769 /* Find last component of program's own name, using some heuristics */
15770 void
15771 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15772 {
15773     char *p, *q, c;
15774     int local = (strcmp(host, "localhost") == 0);
15775     while (!local && (p = strchr(prog, ';')) != NULL) {
15776         p++;
15777         while (*p == ' ') p++;
15778         prog = p;
15779     }
15780     if (*prog == '"' || *prog == '\'') {
15781         q = strchr(prog + 1, *prog);
15782     } else {
15783         q = strchr(prog, ' ');
15784     }
15785     if (q == NULL) q = prog + strlen(prog);
15786     p = q;
15787     while (p >= prog && *p != '/' && *p != '\\') p--;
15788     p++;
15789     if(p == prog && *p == '"') p++;
15790     c = *q; *q = 0;
15791     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15792     memcpy(buf, p, q - p);
15793     buf[q - p] = NULLCHAR;
15794     if (!local) {
15795         strcat(buf, "@");
15796         strcat(buf, host);
15797     }
15798 }
15799
15800 char *
15801 TimeControlTagValue ()
15802 {
15803     char buf[MSG_SIZ];
15804     if (!appData.clockMode) {
15805       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15806     } else if (movesPerSession > 0) {
15807       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15808     } else if (timeIncrement == 0) {
15809       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15810     } else {
15811       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15812     }
15813     return StrSave(buf);
15814 }
15815
15816 void
15817 SetGameInfo ()
15818 {
15819     /* This routine is used only for certain modes */
15820     VariantClass v = gameInfo.variant;
15821     ChessMove r = GameUnfinished;
15822     char *p = NULL;
15823
15824     if(keepInfo) return;
15825
15826     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15827         r = gameInfo.result;
15828         p = gameInfo.resultDetails;
15829         gameInfo.resultDetails = NULL;
15830     }
15831     ClearGameInfo(&gameInfo);
15832     gameInfo.variant = v;
15833
15834     switch (gameMode) {
15835       case MachinePlaysWhite:
15836         gameInfo.event = StrSave( appData.pgnEventHeader );
15837         gameInfo.site = StrSave(HostName());
15838         gameInfo.date = PGNDate();
15839         gameInfo.round = StrSave("-");
15840         gameInfo.white = StrSave(first.tidy);
15841         gameInfo.black = StrSave(UserName());
15842         gameInfo.timeControl = TimeControlTagValue();
15843         break;
15844
15845       case MachinePlaysBlack:
15846         gameInfo.event = StrSave( appData.pgnEventHeader );
15847         gameInfo.site = StrSave(HostName());
15848         gameInfo.date = PGNDate();
15849         gameInfo.round = StrSave("-");
15850         gameInfo.white = StrSave(UserName());
15851         gameInfo.black = StrSave(first.tidy);
15852         gameInfo.timeControl = TimeControlTagValue();
15853         break;
15854
15855       case TwoMachinesPlay:
15856         gameInfo.event = StrSave( appData.pgnEventHeader );
15857         gameInfo.site = StrSave(HostName());
15858         gameInfo.date = PGNDate();
15859         if (roundNr > 0) {
15860             char buf[MSG_SIZ];
15861             snprintf(buf, MSG_SIZ, "%d", roundNr);
15862             gameInfo.round = StrSave(buf);
15863         } else {
15864             gameInfo.round = StrSave("-");
15865         }
15866         if (first.twoMachinesColor[0] == 'w') {
15867             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15868             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15869         } else {
15870             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15871             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15872         }
15873         gameInfo.timeControl = TimeControlTagValue();
15874         break;
15875
15876       case EditGame:
15877         gameInfo.event = StrSave("Edited game");
15878         gameInfo.site = StrSave(HostName());
15879         gameInfo.date = PGNDate();
15880         gameInfo.round = StrSave("-");
15881         gameInfo.white = StrSave("-");
15882         gameInfo.black = StrSave("-");
15883         gameInfo.result = r;
15884         gameInfo.resultDetails = p;
15885         break;
15886
15887       case EditPosition:
15888         gameInfo.event = StrSave("Edited position");
15889         gameInfo.site = StrSave(HostName());
15890         gameInfo.date = PGNDate();
15891         gameInfo.round = StrSave("-");
15892         gameInfo.white = StrSave("-");
15893         gameInfo.black = StrSave("-");
15894         break;
15895
15896       case IcsPlayingWhite:
15897       case IcsPlayingBlack:
15898       case IcsObserving:
15899       case IcsExamining:
15900         break;
15901
15902       case PlayFromGameFile:
15903         gameInfo.event = StrSave("Game from non-PGN file");
15904         gameInfo.site = StrSave(HostName());
15905         gameInfo.date = PGNDate();
15906         gameInfo.round = StrSave("-");
15907         gameInfo.white = StrSave("?");
15908         gameInfo.black = StrSave("?");
15909         break;
15910
15911       default:
15912         break;
15913     }
15914 }
15915
15916 void
15917 ReplaceComment (int index, char *text)
15918 {
15919     int len;
15920     char *p;
15921     float score;
15922
15923     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15924        pvInfoList[index-1].depth == len &&
15925        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15926        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15927     while (*text == '\n') text++;
15928     len = strlen(text);
15929     while (len > 0 && text[len - 1] == '\n') len--;
15930
15931     if (commentList[index] != NULL)
15932       free(commentList[index]);
15933
15934     if (len == 0) {
15935         commentList[index] = NULL;
15936         return;
15937     }
15938   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15939       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15940       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15941     commentList[index] = (char *) malloc(len + 2);
15942     strncpy(commentList[index], text, len);
15943     commentList[index][len] = '\n';
15944     commentList[index][len + 1] = NULLCHAR;
15945   } else {
15946     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15947     char *p;
15948     commentList[index] = (char *) malloc(len + 7);
15949     safeStrCpy(commentList[index], "{\n", 3);
15950     safeStrCpy(commentList[index]+2, text, len+1);
15951     commentList[index][len+2] = NULLCHAR;
15952     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15953     strcat(commentList[index], "\n}\n");
15954   }
15955 }
15956
15957 void
15958 CrushCRs (char *text)
15959 {
15960   char *p = text;
15961   char *q = text;
15962   char ch;
15963
15964   do {
15965     ch = *p++;
15966     if (ch == '\r') continue;
15967     *q++ = ch;
15968   } while (ch != '\0');
15969 }
15970
15971 void
15972 AppendComment (int index, char *text, Boolean addBraces)
15973 /* addBraces  tells if we should add {} */
15974 {
15975     int oldlen, len;
15976     char *old;
15977
15978 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15979     if(addBraces == 3) addBraces = 0; else // force appending literally
15980     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15981
15982     CrushCRs(text);
15983     while (*text == '\n') text++;
15984     len = strlen(text);
15985     while (len > 0 && text[len - 1] == '\n') len--;
15986     text[len] = NULLCHAR;
15987
15988     if (len == 0) return;
15989
15990     if (commentList[index] != NULL) {
15991       Boolean addClosingBrace = addBraces;
15992         old = commentList[index];
15993         oldlen = strlen(old);
15994         while(commentList[index][oldlen-1] ==  '\n')
15995           commentList[index][--oldlen] = NULLCHAR;
15996         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15997         safeStrCpy(commentList[index], old, oldlen + len + 6);
15998         free(old);
15999         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16000         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16001           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16002           while (*text == '\n') { text++; len--; }
16003           commentList[index][--oldlen] = NULLCHAR;
16004       }
16005         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16006         else          strcat(commentList[index], "\n");
16007         strcat(commentList[index], text);
16008         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16009         else          strcat(commentList[index], "\n");
16010     } else {
16011         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16012         if(addBraces)
16013           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16014         else commentList[index][0] = NULLCHAR;
16015         strcat(commentList[index], text);
16016         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16017         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16018     }
16019 }
16020
16021 static char *
16022 FindStr (char * text, char * sub_text)
16023 {
16024     char * result = strstr( text, sub_text );
16025
16026     if( result != NULL ) {
16027         result += strlen( sub_text );
16028     }
16029
16030     return result;
16031 }
16032
16033 /* [AS] Try to extract PV info from PGN comment */
16034 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16035 char *
16036 GetInfoFromComment (int index, char * text)
16037 {
16038     char * sep = text, *p;
16039
16040     if( text != NULL && index > 0 ) {
16041         int score = 0;
16042         int depth = 0;
16043         int time = -1, sec = 0, deci;
16044         char * s_eval = FindStr( text, "[%eval " );
16045         char * s_emt = FindStr( text, "[%emt " );
16046 #if 0
16047         if( s_eval != NULL || s_emt != NULL ) {
16048 #else
16049         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16050 #endif
16051             /* New style */
16052             char delim;
16053
16054             if( s_eval != NULL ) {
16055                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16056                     return text;
16057                 }
16058
16059                 if( delim != ']' ) {
16060                     return text;
16061                 }
16062             }
16063
16064             if( s_emt != NULL ) {
16065             }
16066                 return text;
16067         }
16068         else {
16069             /* We expect something like: [+|-]nnn.nn/dd */
16070             int score_lo = 0;
16071
16072             if(*text != '{') return text; // [HGM] braces: must be normal comment
16073
16074             sep = strchr( text, '/' );
16075             if( sep == NULL || sep < (text+4) ) {
16076                 return text;
16077             }
16078
16079             p = text;
16080             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16081             if(p[1] == '(') { // comment starts with PV
16082                p = strchr(p, ')'); // locate end of PV
16083                if(p == NULL || sep < p+5) return text;
16084                // at this point we have something like "{(.*) +0.23/6 ..."
16085                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16086                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16087                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16088             }
16089             time = -1; sec = -1; deci = -1;
16090             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16091                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16092                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16093                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16094                 return text;
16095             }
16096
16097             if( score_lo < 0 || score_lo >= 100 ) {
16098                 return text;
16099             }
16100
16101             if(sec >= 0) time = 600*time + 10*sec; else
16102             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16103
16104             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16105
16106             /* [HGM] PV time: now locate end of PV info */
16107             while( *++sep >= '0' && *sep <= '9'); // strip depth
16108             if(time >= 0)
16109             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16110             if(sec >= 0)
16111             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16112             if(deci >= 0)
16113             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16114             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16115         }
16116
16117         if( depth <= 0 ) {
16118             return text;
16119         }
16120
16121         if( time < 0 ) {
16122             time = -1;
16123         }
16124
16125         pvInfoList[index-1].depth = depth;
16126         pvInfoList[index-1].score = score;
16127         pvInfoList[index-1].time  = 10*time; // centi-sec
16128         if(*sep == '}') *sep = 0; else *--sep = '{';
16129         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16130     }
16131     return sep;
16132 }
16133
16134 void
16135 SendToProgram (char *message, ChessProgramState *cps)
16136 {
16137     int count, outCount, error;
16138     char buf[MSG_SIZ];
16139
16140     if (cps->pr == NoProc) return;
16141     Attention(cps);
16142
16143     if (appData.debugMode) {
16144         TimeMark now;
16145         GetTimeMark(&now);
16146         fprintf(debugFP, "%ld >%-6s: %s",
16147                 SubtractTimeMarks(&now, &programStartTime),
16148                 cps->which, message);
16149         if(serverFP)
16150             fprintf(serverFP, "%ld >%-6s: %s",
16151                 SubtractTimeMarks(&now, &programStartTime),
16152                 cps->which, message), fflush(serverFP);
16153     }
16154
16155     count = strlen(message);
16156     outCount = OutputToProcess(cps->pr, message, count, &error);
16157     if (outCount < count && !exiting
16158                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16159       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16160       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16161         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16162             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16163                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16164                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16165                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16166             } else {
16167                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16168                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16169                 gameInfo.result = res;
16170             }
16171             gameInfo.resultDetails = StrSave(buf);
16172         }
16173         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16174         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16175     }
16176 }
16177
16178 void
16179 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16180 {
16181     char *end_str;
16182     char buf[MSG_SIZ];
16183     ChessProgramState *cps = (ChessProgramState *)closure;
16184
16185     if (isr != cps->isr) return; /* Killed intentionally */
16186     if (count <= 0) {
16187         if (count == 0) {
16188             RemoveInputSource(cps->isr);
16189             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16190                     _(cps->which), cps->program);
16191             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16192             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16193                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16194                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16195                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16196                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16197                 } else {
16198                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16199                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16200                     gameInfo.result = res;
16201                 }
16202                 gameInfo.resultDetails = StrSave(buf);
16203             }
16204             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16205             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16206         } else {
16207             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16208                     _(cps->which), cps->program);
16209             RemoveInputSource(cps->isr);
16210
16211             /* [AS] Program is misbehaving badly... kill it */
16212             if( count == -2 ) {
16213                 DestroyChildProcess( cps->pr, 9 );
16214                 cps->pr = NoProc;
16215             }
16216
16217             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16218         }
16219         return;
16220     }
16221
16222     if ((end_str = strchr(message, '\r')) != NULL)
16223       *end_str = NULLCHAR;
16224     if ((end_str = strchr(message, '\n')) != NULL)
16225       *end_str = NULLCHAR;
16226
16227     if (appData.debugMode) {
16228         TimeMark now; int print = 1;
16229         char *quote = ""; char c; int i;
16230
16231         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16232                 char start = message[0];
16233                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16234                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16235                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16236                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16237                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16238                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16239                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16240                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16241                    sscanf(message, "hint: %c", &c)!=1 &&
16242                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16243                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16244                     print = (appData.engineComments >= 2);
16245                 }
16246                 message[0] = start; // restore original message
16247         }
16248         if(print) {
16249                 GetTimeMark(&now);
16250                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16251                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16252                         quote,
16253                         message);
16254                 if(serverFP)
16255                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16256                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16257                         quote,
16258                         message), fflush(serverFP);
16259         }
16260     }
16261
16262     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16263     if (appData.icsEngineAnalyze) {
16264         if (strstr(message, "whisper") != NULL ||
16265              strstr(message, "kibitz") != NULL ||
16266             strstr(message, "tellics") != NULL) return;
16267     }
16268
16269     HandleMachineMove(message, cps);
16270 }
16271
16272
16273 void
16274 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16275 {
16276     char buf[MSG_SIZ];
16277     int seconds;
16278
16279     if( timeControl_2 > 0 ) {
16280         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16281             tc = timeControl_2;
16282         }
16283     }
16284     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16285     inc /= cps->timeOdds;
16286     st  /= cps->timeOdds;
16287
16288     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16289
16290     if (st > 0) {
16291       /* Set exact time per move, normally using st command */
16292       if (cps->stKludge) {
16293         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16294         seconds = st % 60;
16295         if (seconds == 0) {
16296           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16297         } else {
16298           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16299         }
16300       } else {
16301         snprintf(buf, MSG_SIZ, "st %d\n", st);
16302       }
16303     } else {
16304       /* Set conventional or incremental time control, using level command */
16305       if (seconds == 0) {
16306         /* Note old gnuchess bug -- minutes:seconds used to not work.
16307            Fixed in later versions, but still avoid :seconds
16308            when seconds is 0. */
16309         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16310       } else {
16311         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16312                  seconds, inc/1000.);
16313       }
16314     }
16315     SendToProgram(buf, cps);
16316
16317     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16318     /* Orthogonally, limit search to given depth */
16319     if (sd > 0) {
16320       if (cps->sdKludge) {
16321         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16322       } else {
16323         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16324       }
16325       SendToProgram(buf, cps);
16326     }
16327
16328     if(cps->nps >= 0) { /* [HGM] nps */
16329         if(cps->supportsNPS == FALSE)
16330           cps->nps = -1; // don't use if engine explicitly says not supported!
16331         else {
16332           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16333           SendToProgram(buf, cps);
16334         }
16335     }
16336 }
16337
16338 ChessProgramState *
16339 WhitePlayer ()
16340 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16341 {
16342     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16343        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16344         return &second;
16345     return &first;
16346 }
16347
16348 void
16349 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16350 {
16351     char message[MSG_SIZ];
16352     long time, otime;
16353
16354     /* Note: this routine must be called when the clocks are stopped
16355        or when they have *just* been set or switched; otherwise
16356        it will be off by the time since the current tick started.
16357     */
16358     if (machineWhite) {
16359         time = whiteTimeRemaining / 10;
16360         otime = blackTimeRemaining / 10;
16361     } else {
16362         time = blackTimeRemaining / 10;
16363         otime = whiteTimeRemaining / 10;
16364     }
16365     /* [HGM] translate opponent's time by time-odds factor */
16366     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16367
16368     if (time <= 0) time = 1;
16369     if (otime <= 0) otime = 1;
16370
16371     snprintf(message, MSG_SIZ, "time %ld\n", time);
16372     SendToProgram(message, cps);
16373
16374     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16375     SendToProgram(message, cps);
16376 }
16377
16378 char *
16379 EngineDefinedVariant (ChessProgramState *cps, int n)
16380 {   // return name of n-th unknown variant that engine supports
16381     static char buf[MSG_SIZ];
16382     char *p, *s = cps->variants;
16383     if(!s) return NULL;
16384     do { // parse string from variants feature
16385       VariantClass v;
16386         p = strchr(s, ',');
16387         if(p) *p = NULLCHAR;
16388       v = StringToVariant(s);
16389       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16390         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16391             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16392         }
16393         if(p) *p++ = ',';
16394         if(n < 0) return buf;
16395     } while(s = p);
16396     return NULL;
16397 }
16398
16399 int
16400 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16401 {
16402   char buf[MSG_SIZ];
16403   int len = strlen(name);
16404   int val;
16405
16406   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16407     (*p) += len + 1;
16408     sscanf(*p, "%d", &val);
16409     *loc = (val != 0);
16410     while (**p && **p != ' ')
16411       (*p)++;
16412     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16413     SendToProgram(buf, cps);
16414     return TRUE;
16415   }
16416   return FALSE;
16417 }
16418
16419 int
16420 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16421 {
16422   char buf[MSG_SIZ];
16423   int len = strlen(name);
16424   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16425     (*p) += len + 1;
16426     sscanf(*p, "%d", loc);
16427     while (**p && **p != ' ') (*p)++;
16428     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16429     SendToProgram(buf, cps);
16430     return TRUE;
16431   }
16432   return FALSE;
16433 }
16434
16435 int
16436 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16437 {
16438   char buf[MSG_SIZ];
16439   int len = strlen(name);
16440   if (strncmp((*p), name, len) == 0
16441       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16442     (*p) += len + 2;
16443     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16444     sscanf(*p, "%[^\"]", *loc);
16445     while (**p && **p != '\"') (*p)++;
16446     if (**p == '\"') (*p)++;
16447     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16448     SendToProgram(buf, cps);
16449     return TRUE;
16450   }
16451   return FALSE;
16452 }
16453
16454 int
16455 ParseOption (Option *opt, ChessProgramState *cps)
16456 // [HGM] options: process the string that defines an engine option, and determine
16457 // name, type, default value, and allowed value range
16458 {
16459         char *p, *q, buf[MSG_SIZ];
16460         int n, min = (-1)<<31, max = 1<<31, def;
16461
16462         if(p = strstr(opt->name, " -spin ")) {
16463             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16464             if(max < min) max = min; // enforce consistency
16465             if(def < min) def = min;
16466             if(def > max) def = max;
16467             opt->value = def;
16468             opt->min = min;
16469             opt->max = max;
16470             opt->type = Spin;
16471         } else if((p = strstr(opt->name, " -slider "))) {
16472             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16473             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16474             if(max < min) max = min; // enforce consistency
16475             if(def < min) def = min;
16476             if(def > max) def = max;
16477             opt->value = def;
16478             opt->min = min;
16479             opt->max = max;
16480             opt->type = Spin; // Slider;
16481         } else if((p = strstr(opt->name, " -string "))) {
16482             opt->textValue = p+9;
16483             opt->type = TextBox;
16484         } else if((p = strstr(opt->name, " -file "))) {
16485             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16486             opt->textValue = p+7;
16487             opt->type = FileName; // FileName;
16488         } else if((p = strstr(opt->name, " -path "))) {
16489             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16490             opt->textValue = p+7;
16491             opt->type = PathName; // PathName;
16492         } else if(p = strstr(opt->name, " -check ")) {
16493             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16494             opt->value = (def != 0);
16495             opt->type = CheckBox;
16496         } else if(p = strstr(opt->name, " -combo ")) {
16497             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16498             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16499             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16500             opt->value = n = 0;
16501             while(q = StrStr(q, " /// ")) {
16502                 n++; *q = 0;    // count choices, and null-terminate each of them
16503                 q += 5;
16504                 if(*q == '*') { // remember default, which is marked with * prefix
16505                     q++;
16506                     opt->value = n;
16507                 }
16508                 cps->comboList[cps->comboCnt++] = q;
16509             }
16510             cps->comboList[cps->comboCnt++] = NULL;
16511             opt->max = n + 1;
16512             opt->type = ComboBox;
16513         } else if(p = strstr(opt->name, " -button")) {
16514             opt->type = Button;
16515         } else if(p = strstr(opt->name, " -save")) {
16516             opt->type = SaveButton;
16517         } else return FALSE;
16518         *p = 0; // terminate option name
16519         // now look if the command-line options define a setting for this engine option.
16520         if(cps->optionSettings && cps->optionSettings[0])
16521             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16522         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16523           snprintf(buf, MSG_SIZ, "option %s", p);
16524                 if(p = strstr(buf, ",")) *p = 0;
16525                 if(q = strchr(buf, '=')) switch(opt->type) {
16526                     case ComboBox:
16527                         for(n=0; n<opt->max; n++)
16528                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16529                         break;
16530                     case TextBox:
16531                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16532                         break;
16533                     case Spin:
16534                     case CheckBox:
16535                         opt->value = atoi(q+1);
16536                     default:
16537                         break;
16538                 }
16539                 strcat(buf, "\n");
16540                 SendToProgram(buf, cps);
16541         }
16542         return TRUE;
16543 }
16544
16545 void
16546 FeatureDone (ChessProgramState *cps, int val)
16547 {
16548   DelayedEventCallback cb = GetDelayedEvent();
16549   if ((cb == InitBackEnd3 && cps == &first) ||
16550       (cb == SettingsMenuIfReady && cps == &second) ||
16551       (cb == LoadEngine) ||
16552       (cb == TwoMachinesEventIfReady)) {
16553     CancelDelayedEvent();
16554     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16555   }
16556   cps->initDone = val;
16557   if(val) cps->reload = FALSE;
16558 }
16559
16560 /* Parse feature command from engine */
16561 void
16562 ParseFeatures (char *args, ChessProgramState *cps)
16563 {
16564   char *p = args;
16565   char *q = NULL;
16566   int val;
16567   char buf[MSG_SIZ];
16568
16569   for (;;) {
16570     while (*p == ' ') p++;
16571     if (*p == NULLCHAR) return;
16572
16573     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16574     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16575     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16576     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16577     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16578     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16579     if (BoolFeature(&p, "reuse", &val, cps)) {
16580       /* Engine can disable reuse, but can't enable it if user said no */
16581       if (!val) cps->reuse = FALSE;
16582       continue;
16583     }
16584     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16585     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16586       if (gameMode == TwoMachinesPlay) {
16587         DisplayTwoMachinesTitle();
16588       } else {
16589         DisplayTitle("");
16590       }
16591       continue;
16592     }
16593     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16594     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16595     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16596     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16597     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16598     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16599     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16600     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16601     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16602     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16603     if (IntFeature(&p, "done", &val, cps)) {
16604       FeatureDone(cps, val);
16605       continue;
16606     }
16607     /* Added by Tord: */
16608     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16609     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16610     /* End of additions by Tord */
16611
16612     /* [HGM] added features: */
16613     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16614     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16615     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16616     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16617     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16618     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16619     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16620     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16621         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16622         FREE(cps->option[cps->nrOptions].name);
16623         cps->option[cps->nrOptions].name = q; q = NULL;
16624         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16625           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16626             SendToProgram(buf, cps);
16627             continue;
16628         }
16629         if(cps->nrOptions >= MAX_OPTIONS) {
16630             cps->nrOptions--;
16631             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16632             DisplayError(buf, 0);
16633         }
16634         continue;
16635     }
16636     /* End of additions by HGM */
16637
16638     /* unknown feature: complain and skip */
16639     q = p;
16640     while (*q && *q != '=') q++;
16641     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16642     SendToProgram(buf, cps);
16643     p = q;
16644     if (*p == '=') {
16645       p++;
16646       if (*p == '\"') {
16647         p++;
16648         while (*p && *p != '\"') p++;
16649         if (*p == '\"') p++;
16650       } else {
16651         while (*p && *p != ' ') p++;
16652       }
16653     }
16654   }
16655
16656 }
16657
16658 void
16659 PeriodicUpdatesEvent (int newState)
16660 {
16661     if (newState == appData.periodicUpdates)
16662       return;
16663
16664     appData.periodicUpdates=newState;
16665
16666     /* Display type changes, so update it now */
16667 //    DisplayAnalysis();
16668
16669     /* Get the ball rolling again... */
16670     if (newState) {
16671         AnalysisPeriodicEvent(1);
16672         StartAnalysisClock();
16673     }
16674 }
16675
16676 void
16677 PonderNextMoveEvent (int newState)
16678 {
16679     if (newState == appData.ponderNextMove) return;
16680     if (gameMode == EditPosition) EditPositionDone(TRUE);
16681     if (newState) {
16682         SendToProgram("hard\n", &first);
16683         if (gameMode == TwoMachinesPlay) {
16684             SendToProgram("hard\n", &second);
16685         }
16686     } else {
16687         SendToProgram("easy\n", &first);
16688         thinkOutput[0] = NULLCHAR;
16689         if (gameMode == TwoMachinesPlay) {
16690             SendToProgram("easy\n", &second);
16691         }
16692     }
16693     appData.ponderNextMove = newState;
16694 }
16695
16696 void
16697 NewSettingEvent (int option, int *feature, char *command, int value)
16698 {
16699     char buf[MSG_SIZ];
16700
16701     if (gameMode == EditPosition) EditPositionDone(TRUE);
16702     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16703     if(feature == NULL || *feature) SendToProgram(buf, &first);
16704     if (gameMode == TwoMachinesPlay) {
16705         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16706     }
16707 }
16708
16709 void
16710 ShowThinkingEvent ()
16711 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16712 {
16713     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16714     int newState = appData.showThinking
16715         // [HGM] thinking: other features now need thinking output as well
16716         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16717
16718     if (oldState == newState) return;
16719     oldState = newState;
16720     if (gameMode == EditPosition) EditPositionDone(TRUE);
16721     if (oldState) {
16722         SendToProgram("post\n", &first);
16723         if (gameMode == TwoMachinesPlay) {
16724             SendToProgram("post\n", &second);
16725         }
16726     } else {
16727         SendToProgram("nopost\n", &first);
16728         thinkOutput[0] = NULLCHAR;
16729         if (gameMode == TwoMachinesPlay) {
16730             SendToProgram("nopost\n", &second);
16731         }
16732     }
16733 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16734 }
16735
16736 void
16737 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16738 {
16739   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16740   if (pr == NoProc) return;
16741   AskQuestion(title, question, replyPrefix, pr);
16742 }
16743
16744 void
16745 TypeInEvent (char firstChar)
16746 {
16747     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16748         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16749         gameMode == AnalyzeMode || gameMode == EditGame ||
16750         gameMode == EditPosition || gameMode == IcsExamining ||
16751         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16752         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16753                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16754                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16755         gameMode == Training) PopUpMoveDialog(firstChar);
16756 }
16757
16758 void
16759 TypeInDoneEvent (char *move)
16760 {
16761         Board board;
16762         int n, fromX, fromY, toX, toY;
16763         char promoChar;
16764         ChessMove moveType;
16765
16766         // [HGM] FENedit
16767         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16768                 EditPositionPasteFEN(move);
16769                 return;
16770         }
16771         // [HGM] movenum: allow move number to be typed in any mode
16772         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16773           ToNrEvent(2*n-1);
16774           return;
16775         }
16776         // undocumented kludge: allow command-line option to be typed in!
16777         // (potentially fatal, and does not implement the effect of the option.)
16778         // should only be used for options that are values on which future decisions will be made,
16779         // and definitely not on options that would be used during initialization.
16780         if(strstr(move, "!!! -") == move) {
16781             ParseArgsFromString(move+4);
16782             return;
16783         }
16784
16785       if (gameMode != EditGame && currentMove != forwardMostMove &&
16786         gameMode != Training) {
16787         DisplayMoveError(_("Displayed move is not current"));
16788       } else {
16789         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16790           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16791         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16792         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16793           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16794           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16795         } else {
16796           DisplayMoveError(_("Could not parse move"));
16797         }
16798       }
16799 }
16800
16801 void
16802 DisplayMove (int moveNumber)
16803 {
16804     char message[MSG_SIZ];
16805     char res[MSG_SIZ];
16806     char cpThinkOutput[MSG_SIZ];
16807
16808     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16809
16810     if (moveNumber == forwardMostMove - 1 ||
16811         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16812
16813         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16814
16815         if (strchr(cpThinkOutput, '\n')) {
16816             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16817         }
16818     } else {
16819         *cpThinkOutput = NULLCHAR;
16820     }
16821
16822     /* [AS] Hide thinking from human user */
16823     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16824         *cpThinkOutput = NULLCHAR;
16825         if( thinkOutput[0] != NULLCHAR ) {
16826             int i;
16827
16828             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16829                 cpThinkOutput[i] = '.';
16830             }
16831             cpThinkOutput[i] = NULLCHAR;
16832             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16833         }
16834     }
16835
16836     if (moveNumber == forwardMostMove - 1 &&
16837         gameInfo.resultDetails != NULL) {
16838         if (gameInfo.resultDetails[0] == NULLCHAR) {
16839           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16840         } else {
16841           snprintf(res, MSG_SIZ, " {%s} %s",
16842                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16843         }
16844     } else {
16845         res[0] = NULLCHAR;
16846     }
16847
16848     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16849         DisplayMessage(res, cpThinkOutput);
16850     } else {
16851       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16852                 WhiteOnMove(moveNumber) ? " " : ".. ",
16853                 parseList[moveNumber], res);
16854         DisplayMessage(message, cpThinkOutput);
16855     }
16856 }
16857
16858 void
16859 DisplayComment (int moveNumber, char *text)
16860 {
16861     char title[MSG_SIZ];
16862
16863     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16864       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16865     } else {
16866       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16867               WhiteOnMove(moveNumber) ? " " : ".. ",
16868               parseList[moveNumber]);
16869     }
16870     if (text != NULL && (appData.autoDisplayComment || commentUp))
16871         CommentPopUp(title, text);
16872 }
16873
16874 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16875  * might be busy thinking or pondering.  It can be omitted if your
16876  * gnuchess is configured to stop thinking immediately on any user
16877  * input.  However, that gnuchess feature depends on the FIONREAD
16878  * ioctl, which does not work properly on some flavors of Unix.
16879  */
16880 void
16881 Attention (ChessProgramState *cps)
16882 {
16883 #if ATTENTION
16884     if (!cps->useSigint) return;
16885     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16886     switch (gameMode) {
16887       case MachinePlaysWhite:
16888       case MachinePlaysBlack:
16889       case TwoMachinesPlay:
16890       case IcsPlayingWhite:
16891       case IcsPlayingBlack:
16892       case AnalyzeMode:
16893       case AnalyzeFile:
16894         /* Skip if we know it isn't thinking */
16895         if (!cps->maybeThinking) return;
16896         if (appData.debugMode)
16897           fprintf(debugFP, "Interrupting %s\n", cps->which);
16898         InterruptChildProcess(cps->pr);
16899         cps->maybeThinking = FALSE;
16900         break;
16901       default:
16902         break;
16903     }
16904 #endif /*ATTENTION*/
16905 }
16906
16907 int
16908 CheckFlags ()
16909 {
16910     if (whiteTimeRemaining <= 0) {
16911         if (!whiteFlag) {
16912             whiteFlag = TRUE;
16913             if (appData.icsActive) {
16914                 if (appData.autoCallFlag &&
16915                     gameMode == IcsPlayingBlack && !blackFlag) {
16916                   SendToICS(ics_prefix);
16917                   SendToICS("flag\n");
16918                 }
16919             } else {
16920                 if (blackFlag) {
16921                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16922                 } else {
16923                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16924                     if (appData.autoCallFlag) {
16925                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16926                         return TRUE;
16927                     }
16928                 }
16929             }
16930         }
16931     }
16932     if (blackTimeRemaining <= 0) {
16933         if (!blackFlag) {
16934             blackFlag = TRUE;
16935             if (appData.icsActive) {
16936                 if (appData.autoCallFlag &&
16937                     gameMode == IcsPlayingWhite && !whiteFlag) {
16938                   SendToICS(ics_prefix);
16939                   SendToICS("flag\n");
16940                 }
16941             } else {
16942                 if (whiteFlag) {
16943                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16944                 } else {
16945                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16946                     if (appData.autoCallFlag) {
16947                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16948                         return TRUE;
16949                     }
16950                 }
16951             }
16952         }
16953     }
16954     return FALSE;
16955 }
16956
16957 void
16958 CheckTimeControl ()
16959 {
16960     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16961         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16962
16963     /*
16964      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16965      */
16966     if ( !WhiteOnMove(forwardMostMove) ) {
16967         /* White made time control */
16968         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16969         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16970         /* [HGM] time odds: correct new time quota for time odds! */
16971                                             / WhitePlayer()->timeOdds;
16972         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16973     } else {
16974         lastBlack -= blackTimeRemaining;
16975         /* Black made time control */
16976         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16977                                             / WhitePlayer()->other->timeOdds;
16978         lastWhite = whiteTimeRemaining;
16979     }
16980 }
16981
16982 void
16983 DisplayBothClocks ()
16984 {
16985     int wom = gameMode == EditPosition ?
16986       !blackPlaysFirst : WhiteOnMove(currentMove);
16987     DisplayWhiteClock(whiteTimeRemaining, wom);
16988     DisplayBlackClock(blackTimeRemaining, !wom);
16989 }
16990
16991
16992 /* Timekeeping seems to be a portability nightmare.  I think everyone
16993    has ftime(), but I'm really not sure, so I'm including some ifdefs
16994    to use other calls if you don't.  Clocks will be less accurate if
16995    you have neither ftime nor gettimeofday.
16996 */
16997
16998 /* VS 2008 requires the #include outside of the function */
16999 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17000 #include <sys/timeb.h>
17001 #endif
17002
17003 /* Get the current time as a TimeMark */
17004 void
17005 GetTimeMark (TimeMark *tm)
17006 {
17007 #if HAVE_GETTIMEOFDAY
17008
17009     struct timeval timeVal;
17010     struct timezone timeZone;
17011
17012     gettimeofday(&timeVal, &timeZone);
17013     tm->sec = (long) timeVal.tv_sec;
17014     tm->ms = (int) (timeVal.tv_usec / 1000L);
17015
17016 #else /*!HAVE_GETTIMEOFDAY*/
17017 #if HAVE_FTIME
17018
17019 // include <sys/timeb.h> / moved to just above start of function
17020     struct timeb timeB;
17021
17022     ftime(&timeB);
17023     tm->sec = (long) timeB.time;
17024     tm->ms = (int) timeB.millitm;
17025
17026 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17027     tm->sec = (long) time(NULL);
17028     tm->ms = 0;
17029 #endif
17030 #endif
17031 }
17032
17033 /* Return the difference in milliseconds between two
17034    time marks.  We assume the difference will fit in a long!
17035 */
17036 long
17037 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17038 {
17039     return 1000L*(tm2->sec - tm1->sec) +
17040            (long) (tm2->ms - tm1->ms);
17041 }
17042
17043
17044 /*
17045  * Code to manage the game clocks.
17046  *
17047  * In tournament play, black starts the clock and then white makes a move.
17048  * We give the human user a slight advantage if he is playing white---the
17049  * clocks don't run until he makes his first move, so it takes zero time.
17050  * Also, we don't account for network lag, so we could get out of sync
17051  * with GNU Chess's clock -- but then, referees are always right.
17052  */
17053
17054 static TimeMark tickStartTM;
17055 static long intendedTickLength;
17056
17057 long
17058 NextTickLength (long timeRemaining)
17059 {
17060     long nominalTickLength, nextTickLength;
17061
17062     if (timeRemaining > 0L && timeRemaining <= 10000L)
17063       nominalTickLength = 100L;
17064     else
17065       nominalTickLength = 1000L;
17066     nextTickLength = timeRemaining % nominalTickLength;
17067     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17068
17069     return nextTickLength;
17070 }
17071
17072 /* Adjust clock one minute up or down */
17073 void
17074 AdjustClock (Boolean which, int dir)
17075 {
17076     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17077     if(which) blackTimeRemaining += 60000*dir;
17078     else      whiteTimeRemaining += 60000*dir;
17079     DisplayBothClocks();
17080     adjustedClock = TRUE;
17081 }
17082
17083 /* Stop clocks and reset to a fresh time control */
17084 void
17085 ResetClocks ()
17086 {
17087     (void) StopClockTimer();
17088     if (appData.icsActive) {
17089         whiteTimeRemaining = blackTimeRemaining = 0;
17090     } else if (searchTime) {
17091         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17092         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17093     } else { /* [HGM] correct new time quote for time odds */
17094         whiteTC = blackTC = fullTimeControlString;
17095         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17096         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17097     }
17098     if (whiteFlag || blackFlag) {
17099         DisplayTitle("");
17100         whiteFlag = blackFlag = FALSE;
17101     }
17102     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17103     DisplayBothClocks();
17104     adjustedClock = FALSE;
17105 }
17106
17107 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17108
17109 /* Decrement running clock by amount of time that has passed */
17110 void
17111 DecrementClocks ()
17112 {
17113     long timeRemaining;
17114     long lastTickLength, fudge;
17115     TimeMark now;
17116
17117     if (!appData.clockMode) return;
17118     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17119
17120     GetTimeMark(&now);
17121
17122     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17123
17124     /* Fudge if we woke up a little too soon */
17125     fudge = intendedTickLength - lastTickLength;
17126     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17127
17128     if (WhiteOnMove(forwardMostMove)) {
17129         if(whiteNPS >= 0) lastTickLength = 0;
17130         timeRemaining = whiteTimeRemaining -= lastTickLength;
17131         if(timeRemaining < 0 && !appData.icsActive) {
17132             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17133             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17134                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17135                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17136             }
17137         }
17138         DisplayWhiteClock(whiteTimeRemaining - fudge,
17139                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17140     } else {
17141         if(blackNPS >= 0) lastTickLength = 0;
17142         timeRemaining = blackTimeRemaining -= lastTickLength;
17143         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17144             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17145             if(suddenDeath) {
17146                 blackStartMove = forwardMostMove;
17147                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17148             }
17149         }
17150         DisplayBlackClock(blackTimeRemaining - fudge,
17151                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17152     }
17153     if (CheckFlags()) return;
17154
17155     if(twoBoards) { // count down secondary board's clocks as well
17156         activePartnerTime -= lastTickLength;
17157         partnerUp = 1;
17158         if(activePartner == 'W')
17159             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17160         else
17161             DisplayBlackClock(activePartnerTime, TRUE);
17162         partnerUp = 0;
17163     }
17164
17165     tickStartTM = now;
17166     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17167     StartClockTimer(intendedTickLength);
17168
17169     /* if the time remaining has fallen below the alarm threshold, sound the
17170      * alarm. if the alarm has sounded and (due to a takeback or time control
17171      * with increment) the time remaining has increased to a level above the
17172      * threshold, reset the alarm so it can sound again.
17173      */
17174
17175     if (appData.icsActive && appData.icsAlarm) {
17176
17177         /* make sure we are dealing with the user's clock */
17178         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17179                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17180            )) return;
17181
17182         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17183             alarmSounded = FALSE;
17184         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17185             PlayAlarmSound();
17186             alarmSounded = TRUE;
17187         }
17188     }
17189 }
17190
17191
17192 /* A player has just moved, so stop the previously running
17193    clock and (if in clock mode) start the other one.
17194    We redisplay both clocks in case we're in ICS mode, because
17195    ICS gives us an update to both clocks after every move.
17196    Note that this routine is called *after* forwardMostMove
17197    is updated, so the last fractional tick must be subtracted
17198    from the color that is *not* on move now.
17199 */
17200 void
17201 SwitchClocks (int newMoveNr)
17202 {
17203     long lastTickLength;
17204     TimeMark now;
17205     int flagged = FALSE;
17206
17207     GetTimeMark(&now);
17208
17209     if (StopClockTimer() && appData.clockMode) {
17210         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17211         if (!WhiteOnMove(forwardMostMove)) {
17212             if(blackNPS >= 0) lastTickLength = 0;
17213             blackTimeRemaining -= lastTickLength;
17214            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17215 //         if(pvInfoList[forwardMostMove].time == -1)
17216                  pvInfoList[forwardMostMove].time =               // use GUI time
17217                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17218         } else {
17219            if(whiteNPS >= 0) lastTickLength = 0;
17220            whiteTimeRemaining -= lastTickLength;
17221            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17222 //         if(pvInfoList[forwardMostMove].time == -1)
17223                  pvInfoList[forwardMostMove].time =
17224                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17225         }
17226         flagged = CheckFlags();
17227     }
17228     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17229     CheckTimeControl();
17230
17231     if (flagged || !appData.clockMode) return;
17232
17233     switch (gameMode) {
17234       case MachinePlaysBlack:
17235       case MachinePlaysWhite:
17236       case BeginningOfGame:
17237         if (pausing) return;
17238         break;
17239
17240       case EditGame:
17241       case PlayFromGameFile:
17242       case IcsExamining:
17243         return;
17244
17245       default:
17246         break;
17247     }
17248
17249     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17250         if(WhiteOnMove(forwardMostMove))
17251              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17252         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17253     }
17254
17255     tickStartTM = now;
17256     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17257       whiteTimeRemaining : blackTimeRemaining);
17258     StartClockTimer(intendedTickLength);
17259 }
17260
17261
17262 /* Stop both clocks */
17263 void
17264 StopClocks ()
17265 {
17266     long lastTickLength;
17267     TimeMark now;
17268
17269     if (!StopClockTimer()) return;
17270     if (!appData.clockMode) return;
17271
17272     GetTimeMark(&now);
17273
17274     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17275     if (WhiteOnMove(forwardMostMove)) {
17276         if(whiteNPS >= 0) lastTickLength = 0;
17277         whiteTimeRemaining -= lastTickLength;
17278         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17279     } else {
17280         if(blackNPS >= 0) lastTickLength = 0;
17281         blackTimeRemaining -= lastTickLength;
17282         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17283     }
17284     CheckFlags();
17285 }
17286
17287 /* Start clock of player on move.  Time may have been reset, so
17288    if clock is already running, stop and restart it. */
17289 void
17290 StartClocks ()
17291 {
17292     (void) StopClockTimer(); /* in case it was running already */
17293     DisplayBothClocks();
17294     if (CheckFlags()) return;
17295
17296     if (!appData.clockMode) return;
17297     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17298
17299     GetTimeMark(&tickStartTM);
17300     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17301       whiteTimeRemaining : blackTimeRemaining);
17302
17303    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17304     whiteNPS = blackNPS = -1;
17305     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17306        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17307         whiteNPS = first.nps;
17308     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17309        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17310         blackNPS = first.nps;
17311     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17312         whiteNPS = second.nps;
17313     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17314         blackNPS = second.nps;
17315     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17316
17317     StartClockTimer(intendedTickLength);
17318 }
17319
17320 char *
17321 TimeString (long ms)
17322 {
17323     long second, minute, hour, day;
17324     char *sign = "";
17325     static char buf[32];
17326
17327     if (ms > 0 && ms <= 9900) {
17328       /* convert milliseconds to tenths, rounding up */
17329       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17330
17331       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17332       return buf;
17333     }
17334
17335     /* convert milliseconds to seconds, rounding up */
17336     /* use floating point to avoid strangeness of integer division
17337        with negative dividends on many machines */
17338     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17339
17340     if (second < 0) {
17341         sign = "-";
17342         second = -second;
17343     }
17344
17345     day = second / (60 * 60 * 24);
17346     second = second % (60 * 60 * 24);
17347     hour = second / (60 * 60);
17348     second = second % (60 * 60);
17349     minute = second / 60;
17350     second = second % 60;
17351
17352     if (day > 0)
17353       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17354               sign, day, hour, minute, second);
17355     else if (hour > 0)
17356       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17357     else
17358       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17359
17360     return buf;
17361 }
17362
17363
17364 /*
17365  * This is necessary because some C libraries aren't ANSI C compliant yet.
17366  */
17367 char *
17368 StrStr (char *string, char *match)
17369 {
17370     int i, length;
17371
17372     length = strlen(match);
17373
17374     for (i = strlen(string) - length; i >= 0; i--, string++)
17375       if (!strncmp(match, string, length))
17376         return string;
17377
17378     return NULL;
17379 }
17380
17381 char *
17382 StrCaseStr (char *string, char *match)
17383 {
17384     int i, j, length;
17385
17386     length = strlen(match);
17387
17388     for (i = strlen(string) - length; i >= 0; i--, string++) {
17389         for (j = 0; j < length; j++) {
17390             if (ToLower(match[j]) != ToLower(string[j]))
17391               break;
17392         }
17393         if (j == length) return string;
17394     }
17395
17396     return NULL;
17397 }
17398
17399 #ifndef _amigados
17400 int
17401 StrCaseCmp (char *s1, char *s2)
17402 {
17403     char c1, c2;
17404
17405     for (;;) {
17406         c1 = ToLower(*s1++);
17407         c2 = ToLower(*s2++);
17408         if (c1 > c2) return 1;
17409         if (c1 < c2) return -1;
17410         if (c1 == NULLCHAR) return 0;
17411     }
17412 }
17413
17414
17415 int
17416 ToLower (int c)
17417 {
17418     return isupper(c) ? tolower(c) : c;
17419 }
17420
17421
17422 int
17423 ToUpper (int c)
17424 {
17425     return islower(c) ? toupper(c) : c;
17426 }
17427 #endif /* !_amigados    */
17428
17429 char *
17430 StrSave (char *s)
17431 {
17432   char *ret;
17433
17434   if ((ret = (char *) malloc(strlen(s) + 1)))
17435     {
17436       safeStrCpy(ret, s, strlen(s)+1);
17437     }
17438   return ret;
17439 }
17440
17441 char *
17442 StrSavePtr (char *s, char **savePtr)
17443 {
17444     if (*savePtr) {
17445         free(*savePtr);
17446     }
17447     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17448       safeStrCpy(*savePtr, s, strlen(s)+1);
17449     }
17450     return(*savePtr);
17451 }
17452
17453 char *
17454 PGNDate ()
17455 {
17456     time_t clock;
17457     struct tm *tm;
17458     char buf[MSG_SIZ];
17459
17460     clock = time((time_t *)NULL);
17461     tm = localtime(&clock);
17462     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17463             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17464     return StrSave(buf);
17465 }
17466
17467
17468 char *
17469 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17470 {
17471     int i, j, fromX, fromY, toX, toY;
17472     int whiteToPlay;
17473     char buf[MSG_SIZ];
17474     char *p, *q;
17475     int emptycount;
17476     ChessSquare piece;
17477
17478     whiteToPlay = (gameMode == EditPosition) ?
17479       !blackPlaysFirst : (move % 2 == 0);
17480     p = buf;
17481
17482     /* Piece placement data */
17483     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17484         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17485         emptycount = 0;
17486         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17487             if (boards[move][i][j] == EmptySquare) {
17488                 emptycount++;
17489             } else { ChessSquare piece = boards[move][i][j];
17490                 if (emptycount > 0) {
17491                     if(emptycount<10) /* [HGM] can be >= 10 */
17492                         *p++ = '0' + emptycount;
17493                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17494                     emptycount = 0;
17495                 }
17496                 if(PieceToChar(piece) == '+') {
17497                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17498                     *p++ = '+';
17499                     piece = (ChessSquare)(DEMOTED piece);
17500                 }
17501                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17502                 if(p[-1] == '~') {
17503                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17504                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17505                     *p++ = '~';
17506                 }
17507             }
17508         }
17509         if (emptycount > 0) {
17510             if(emptycount<10) /* [HGM] can be >= 10 */
17511                 *p++ = '0' + emptycount;
17512             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17513             emptycount = 0;
17514         }
17515         *p++ = '/';
17516     }
17517     *(p - 1) = ' ';
17518
17519     /* [HGM] print Crazyhouse or Shogi holdings */
17520     if( gameInfo.holdingsWidth ) {
17521         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17522         q = p;
17523         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17524             piece = boards[move][i][BOARD_WIDTH-1];
17525             if( piece != EmptySquare )
17526               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17527                   *p++ = PieceToChar(piece);
17528         }
17529         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17530             piece = boards[move][BOARD_HEIGHT-i-1][0];
17531             if( piece != EmptySquare )
17532               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17533                   *p++ = PieceToChar(piece);
17534         }
17535
17536         if( q == p ) *p++ = '-';
17537         *p++ = ']';
17538         *p++ = ' ';
17539     }
17540
17541     /* Active color */
17542     *p++ = whiteToPlay ? 'w' : 'b';
17543     *p++ = ' ';
17544
17545   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17546     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17547   } else {
17548   if(nrCastlingRights) {
17549      q = p;
17550      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17551        /* [HGM] write directly from rights */
17552            if(boards[move][CASTLING][2] != NoRights &&
17553               boards[move][CASTLING][0] != NoRights   )
17554                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17555            if(boards[move][CASTLING][2] != NoRights &&
17556               boards[move][CASTLING][1] != NoRights   )
17557                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17558            if(boards[move][CASTLING][5] != NoRights &&
17559               boards[move][CASTLING][3] != NoRights   )
17560                 *p++ = boards[move][CASTLING][3] + AAA;
17561            if(boards[move][CASTLING][5] != NoRights &&
17562               boards[move][CASTLING][4] != NoRights   )
17563                 *p++ = boards[move][CASTLING][4] + AAA;
17564      } else {
17565
17566         /* [HGM] write true castling rights */
17567         if( nrCastlingRights == 6 ) {
17568             int q, k=0;
17569             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17570                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17571             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17572                  boards[move][CASTLING][2] != NoRights  );
17573             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17574                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17575                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17576                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17577                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17578             }
17579             if(q) *p++ = 'Q';
17580             k = 0;
17581             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17582                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17583             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17584                  boards[move][CASTLING][5] != NoRights  );
17585             if(gameInfo.variant == VariantSChess) {
17586                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17587                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17588                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17589                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17590             }
17591             if(q) *p++ = 'q';
17592         }
17593      }
17594      if (q == p) *p++ = '-'; /* No castling rights */
17595      *p++ = ' ';
17596   }
17597
17598   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17599      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17600      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17601     /* En passant target square */
17602     if (move > backwardMostMove) {
17603         fromX = moveList[move - 1][0] - AAA;
17604         fromY = moveList[move - 1][1] - ONE;
17605         toX = moveList[move - 1][2] - AAA;
17606         toY = moveList[move - 1][3] - ONE;
17607         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17608             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17609             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17610             fromX == toX) {
17611             /* 2-square pawn move just happened */
17612             *p++ = toX + AAA;
17613             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17614         } else {
17615             *p++ = '-';
17616         }
17617     } else if(move == backwardMostMove) {
17618         // [HGM] perhaps we should always do it like this, and forget the above?
17619         if((signed char)boards[move][EP_STATUS] >= 0) {
17620             *p++ = boards[move][EP_STATUS] + AAA;
17621             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17622         } else {
17623             *p++ = '-';
17624         }
17625     } else {
17626         *p++ = '-';
17627     }
17628     *p++ = ' ';
17629   }
17630   }
17631
17632     if(moveCounts)
17633     {   int i = 0, j=move;
17634
17635         /* [HGM] find reversible plies */
17636         if (appData.debugMode) { int k;
17637             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17638             for(k=backwardMostMove; k<=forwardMostMove; k++)
17639                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17640
17641         }
17642
17643         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17644         if( j == backwardMostMove ) i += initialRulePlies;
17645         sprintf(p, "%d ", i);
17646         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17647
17648         /* Fullmove number */
17649         sprintf(p, "%d", (move / 2) + 1);
17650     } else *--p = NULLCHAR;
17651
17652     return StrSave(buf);
17653 }
17654
17655 Boolean
17656 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17657 {
17658     int i, j, k, w=0;
17659     char *p, c;
17660     int emptycount, virgin[BOARD_FILES];
17661     ChessSquare piece;
17662
17663     p = fen;
17664
17665     /* Piece placement data */
17666     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17667         j = 0;
17668         for (;;) {
17669             if (*p == '/' || *p == ' ' || *p == '[' ) {
17670                 if(j > w) w = j;
17671                 emptycount = gameInfo.boardWidth - j;
17672                 while (emptycount--)
17673                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17674                 if (*p == '/') p++;
17675                 else if(autoSize) { // we stumbled unexpectedly into end of board
17676                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17677                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17678                     }
17679                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17680                 }
17681                 break;
17682 #if(BOARD_FILES >= 10)
17683             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17684                 p++; emptycount=10;
17685                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17686                 while (emptycount--)
17687                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17688 #endif
17689             } else if (*p == '*') {
17690                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17691             } else if (isdigit(*p)) {
17692                 emptycount = *p++ - '0';
17693                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17694                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17695                 while (emptycount--)
17696                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17697             } else if (*p == '+' || isalpha(*p)) {
17698                 if (j >= gameInfo.boardWidth) return FALSE;
17699                 if(*p=='+') {
17700                     piece = CharToPiece(*++p);
17701                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17702                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17703                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17704                 } else piece = CharToPiece(*p++);
17705
17706                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17707                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17708                     piece = (ChessSquare) (PROMOTED piece);
17709                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17710                     p++;
17711                 }
17712                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17713             } else {
17714                 return FALSE;
17715             }
17716         }
17717     }
17718     while (*p == '/' || *p == ' ') p++;
17719
17720     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17721
17722     /* [HGM] by default clear Crazyhouse holdings, if present */
17723     if(gameInfo.holdingsWidth) {
17724        for(i=0; i<BOARD_HEIGHT; i++) {
17725            board[i][0]             = EmptySquare; /* black holdings */
17726            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17727            board[i][1]             = (ChessSquare) 0; /* black counts */
17728            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17729        }
17730     }
17731
17732     /* [HGM] look for Crazyhouse holdings here */
17733     while(*p==' ') p++;
17734     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17735         if(*p == '[') p++;
17736         if(*p == '-' ) p++; /* empty holdings */ else {
17737             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17738             /* if we would allow FEN reading to set board size, we would   */
17739             /* have to add holdings and shift the board read so far here   */
17740             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17741                 p++;
17742                 if((int) piece >= (int) BlackPawn ) {
17743                     i = (int)piece - (int)BlackPawn;
17744                     i = PieceToNumber((ChessSquare)i);
17745                     if( i >= gameInfo.holdingsSize ) return FALSE;
17746                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17747                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17748                 } else {
17749                     i = (int)piece - (int)WhitePawn;
17750                     i = PieceToNumber((ChessSquare)i);
17751                     if( i >= gameInfo.holdingsSize ) return FALSE;
17752                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17753                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17754                 }
17755             }
17756         }
17757         if(*p == ']') p++;
17758     }
17759
17760     while(*p == ' ') p++;
17761
17762     /* Active color */
17763     c = *p++;
17764     if(appData.colorNickNames) {
17765       if( c == appData.colorNickNames[0] ) c = 'w'; else
17766       if( c == appData.colorNickNames[1] ) c = 'b';
17767     }
17768     switch (c) {
17769       case 'w':
17770         *blackPlaysFirst = FALSE;
17771         break;
17772       case 'b':
17773         *blackPlaysFirst = TRUE;
17774         break;
17775       default:
17776         return FALSE;
17777     }
17778
17779     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17780     /* return the extra info in global variiables             */
17781
17782     /* set defaults in case FEN is incomplete */
17783     board[EP_STATUS] = EP_UNKNOWN;
17784     for(i=0; i<nrCastlingRights; i++ ) {
17785         board[CASTLING][i] =
17786             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17787     }   /* assume possible unless obviously impossible */
17788     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17789     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17790     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17791                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17792     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17793     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17794     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17795                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17796     FENrulePlies = 0;
17797
17798     while(*p==' ') p++;
17799     if(nrCastlingRights) {
17800       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17801       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17802           /* castling indicator present, so default becomes no castlings */
17803           for(i=0; i<nrCastlingRights; i++ ) {
17804                  board[CASTLING][i] = NoRights;
17805           }
17806       }
17807       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17808              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17809              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17810              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17811         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17812
17813         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17814             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17815             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17816         }
17817         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17818             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17819         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17820                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17821         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17822                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17823         switch(c) {
17824           case'K':
17825               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17826               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17827               board[CASTLING][2] = whiteKingFile;
17828               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17829               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17830               break;
17831           case'Q':
17832               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17833               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17834               board[CASTLING][2] = whiteKingFile;
17835               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17836               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17837               break;
17838           case'k':
17839               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17840               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17841               board[CASTLING][5] = blackKingFile;
17842               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17843               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17844               break;
17845           case'q':
17846               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17847               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17848               board[CASTLING][5] = blackKingFile;
17849               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17850               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17851           case '-':
17852               break;
17853           default: /* FRC castlings */
17854               if(c >= 'a') { /* black rights */
17855                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17856                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17857                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17858                   if(i == BOARD_RGHT) break;
17859                   board[CASTLING][5] = i;
17860                   c -= AAA;
17861                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17862                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17863                   if(c > i)
17864                       board[CASTLING][3] = c;
17865                   else
17866                       board[CASTLING][4] = c;
17867               } else { /* white rights */
17868                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17869                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17870                     if(board[0][i] == WhiteKing) break;
17871                   if(i == BOARD_RGHT) break;
17872                   board[CASTLING][2] = i;
17873                   c -= AAA - 'a' + 'A';
17874                   if(board[0][c] >= WhiteKing) break;
17875                   if(c > i)
17876                       board[CASTLING][0] = c;
17877                   else
17878                       board[CASTLING][1] = c;
17879               }
17880         }
17881       }
17882       for(i=0; i<nrCastlingRights; i++)
17883         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17884       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17885     if (appData.debugMode) {
17886         fprintf(debugFP, "FEN castling rights:");
17887         for(i=0; i<nrCastlingRights; i++)
17888         fprintf(debugFP, " %d", board[CASTLING][i]);
17889         fprintf(debugFP, "\n");
17890     }
17891
17892       while(*p==' ') p++;
17893     }
17894
17895     /* read e.p. field in games that know e.p. capture */
17896     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17897        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17898        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17899       if(*p=='-') {
17900         p++; board[EP_STATUS] = EP_NONE;
17901       } else {
17902          char c = *p++ - AAA;
17903
17904          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17905          if(*p >= '0' && *p <='9') p++;
17906          board[EP_STATUS] = c;
17907       }
17908     }
17909
17910
17911     if(sscanf(p, "%d", &i) == 1) {
17912         FENrulePlies = i; /* 50-move ply counter */
17913         /* (The move number is still ignored)    */
17914     }
17915
17916     return TRUE;
17917 }
17918
17919 void
17920 EditPositionPasteFEN (char *fen)
17921 {
17922   if (fen != NULL) {
17923     Board initial_position;
17924
17925     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17926       DisplayError(_("Bad FEN position in clipboard"), 0);
17927       return ;
17928     } else {
17929       int savedBlackPlaysFirst = blackPlaysFirst;
17930       EditPositionEvent();
17931       blackPlaysFirst = savedBlackPlaysFirst;
17932       CopyBoard(boards[0], initial_position);
17933       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17934       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17935       DisplayBothClocks();
17936       DrawPosition(FALSE, boards[currentMove]);
17937     }
17938   }
17939 }
17940
17941 static char cseq[12] = "\\   ";
17942
17943 Boolean
17944 set_cont_sequence (char *new_seq)
17945 {
17946     int len;
17947     Boolean ret;
17948
17949     // handle bad attempts to set the sequence
17950         if (!new_seq)
17951                 return 0; // acceptable error - no debug
17952
17953     len = strlen(new_seq);
17954     ret = (len > 0) && (len < sizeof(cseq));
17955     if (ret)
17956       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17957     else if (appData.debugMode)
17958       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17959     return ret;
17960 }
17961
17962 /*
17963     reformat a source message so words don't cross the width boundary.  internal
17964     newlines are not removed.  returns the wrapped size (no null character unless
17965     included in source message).  If dest is NULL, only calculate the size required
17966     for the dest buffer.  lp argument indicats line position upon entry, and it's
17967     passed back upon exit.
17968 */
17969 int
17970 wrap (char *dest, char *src, int count, int width, int *lp)
17971 {
17972     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17973
17974     cseq_len = strlen(cseq);
17975     old_line = line = *lp;
17976     ansi = len = clen = 0;
17977
17978     for (i=0; i < count; i++)
17979     {
17980         if (src[i] == '\033')
17981             ansi = 1;
17982
17983         // if we hit the width, back up
17984         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17985         {
17986             // store i & len in case the word is too long
17987             old_i = i, old_len = len;
17988
17989             // find the end of the last word
17990             while (i && src[i] != ' ' && src[i] != '\n')
17991             {
17992                 i--;
17993                 len--;
17994             }
17995
17996             // word too long?  restore i & len before splitting it
17997             if ((old_i-i+clen) >= width)
17998             {
17999                 i = old_i;
18000                 len = old_len;
18001             }
18002
18003             // extra space?
18004             if (i && src[i-1] == ' ')
18005                 len--;
18006
18007             if (src[i] != ' ' && src[i] != '\n')
18008             {
18009                 i--;
18010                 if (len)
18011                     len--;
18012             }
18013
18014             // now append the newline and continuation sequence
18015             if (dest)
18016                 dest[len] = '\n';
18017             len++;
18018             if (dest)
18019                 strncpy(dest+len, cseq, cseq_len);
18020             len += cseq_len;
18021             line = cseq_len;
18022             clen = cseq_len;
18023             continue;
18024         }
18025
18026         if (dest)
18027             dest[len] = src[i];
18028         len++;
18029         if (!ansi)
18030             line++;
18031         if (src[i] == '\n')
18032             line = 0;
18033         if (src[i] == 'm')
18034             ansi = 0;
18035     }
18036     if (dest && appData.debugMode)
18037     {
18038         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18039             count, width, line, len, *lp);
18040         show_bytes(debugFP, src, count);
18041         fprintf(debugFP, "\ndest: ");
18042         show_bytes(debugFP, dest, len);
18043         fprintf(debugFP, "\n");
18044     }
18045     *lp = dest ? line : old_line;
18046
18047     return len;
18048 }
18049
18050 // [HGM] vari: routines for shelving variations
18051 Boolean modeRestore = FALSE;
18052
18053 void
18054 PushInner (int firstMove, int lastMove)
18055 {
18056         int i, j, nrMoves = lastMove - firstMove;
18057
18058         // push current tail of game on stack
18059         savedResult[storedGames] = gameInfo.result;
18060         savedDetails[storedGames] = gameInfo.resultDetails;
18061         gameInfo.resultDetails = NULL;
18062         savedFirst[storedGames] = firstMove;
18063         savedLast [storedGames] = lastMove;
18064         savedFramePtr[storedGames] = framePtr;
18065         framePtr -= nrMoves; // reserve space for the boards
18066         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18067             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18068             for(j=0; j<MOVE_LEN; j++)
18069                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18070             for(j=0; j<2*MOVE_LEN; j++)
18071                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18072             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18073             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18074             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18075             pvInfoList[firstMove+i-1].depth = 0;
18076             commentList[framePtr+i] = commentList[firstMove+i];
18077             commentList[firstMove+i] = NULL;
18078         }
18079
18080         storedGames++;
18081         forwardMostMove = firstMove; // truncate game so we can start variation
18082 }
18083
18084 void
18085 PushTail (int firstMove, int lastMove)
18086 {
18087         if(appData.icsActive) { // only in local mode
18088                 forwardMostMove = currentMove; // mimic old ICS behavior
18089                 return;
18090         }
18091         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18092
18093         PushInner(firstMove, lastMove);
18094         if(storedGames == 1) GreyRevert(FALSE);
18095         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18096 }
18097
18098 void
18099 PopInner (Boolean annotate)
18100 {
18101         int i, j, nrMoves;
18102         char buf[8000], moveBuf[20];
18103
18104         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18105         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18106         nrMoves = savedLast[storedGames] - currentMove;
18107         if(annotate) {
18108                 int cnt = 10;
18109                 if(!WhiteOnMove(currentMove))
18110                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18111                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18112                 for(i=currentMove; i<forwardMostMove; i++) {
18113                         if(WhiteOnMove(i))
18114                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18115                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18116                         strcat(buf, moveBuf);
18117                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18118                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18119                 }
18120                 strcat(buf, ")");
18121         }
18122         for(i=1; i<=nrMoves; i++) { // copy last variation back
18123             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18124             for(j=0; j<MOVE_LEN; j++)
18125                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18126             for(j=0; j<2*MOVE_LEN; j++)
18127                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18128             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18129             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18130             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18131             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18132             commentList[currentMove+i] = commentList[framePtr+i];
18133             commentList[framePtr+i] = NULL;
18134         }
18135         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18136         framePtr = savedFramePtr[storedGames];
18137         gameInfo.result = savedResult[storedGames];
18138         if(gameInfo.resultDetails != NULL) {
18139             free(gameInfo.resultDetails);
18140       }
18141         gameInfo.resultDetails = savedDetails[storedGames];
18142         forwardMostMove = currentMove + nrMoves;
18143 }
18144
18145 Boolean
18146 PopTail (Boolean annotate)
18147 {
18148         if(appData.icsActive) return FALSE; // only in local mode
18149         if(!storedGames) return FALSE; // sanity
18150         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18151
18152         PopInner(annotate);
18153         if(currentMove < forwardMostMove) ForwardEvent(); else
18154         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18155
18156         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18157         return TRUE;
18158 }
18159
18160 void
18161 CleanupTail ()
18162 {       // remove all shelved variations
18163         int i;
18164         for(i=0; i<storedGames; i++) {
18165             if(savedDetails[i])
18166                 free(savedDetails[i]);
18167             savedDetails[i] = NULL;
18168         }
18169         for(i=framePtr; i<MAX_MOVES; i++) {
18170                 if(commentList[i]) free(commentList[i]);
18171                 commentList[i] = NULL;
18172         }
18173         framePtr = MAX_MOVES-1;
18174         storedGames = 0;
18175 }
18176
18177 void
18178 LoadVariation (int index, char *text)
18179 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18180         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18181         int level = 0, move;
18182
18183         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18184         // first find outermost bracketing variation
18185         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18186             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18187                 if(*p == '{') wait = '}'; else
18188                 if(*p == '[') wait = ']'; else
18189                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18190                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18191             }
18192             if(*p == wait) wait = NULLCHAR; // closing ]} found
18193             p++;
18194         }
18195         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18196         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18197         end[1] = NULLCHAR; // clip off comment beyond variation
18198         ToNrEvent(currentMove-1);
18199         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18200         // kludge: use ParsePV() to append variation to game
18201         move = currentMove;
18202         ParsePV(start, TRUE, TRUE);
18203         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18204         ClearPremoveHighlights();
18205         CommentPopDown();
18206         ToNrEvent(currentMove+1);
18207 }
18208
18209 void
18210 LoadTheme ()
18211 {
18212     char *p, *q, buf[MSG_SIZ];
18213     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18214         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18215         ParseArgsFromString(buf);
18216         ActivateTheme(TRUE); // also redo colors
18217         return;
18218     }
18219     p = nickName;
18220     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18221     {
18222         int len;
18223         q = appData.themeNames;
18224         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18225       if(appData.useBitmaps) {
18226         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18227                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18228                 appData.liteBackTextureMode,
18229                 appData.darkBackTextureMode );
18230       } else {
18231         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18232                 Col2Text(2),   // lightSquareColor
18233                 Col2Text(3) ); // darkSquareColor
18234       }
18235       if(appData.useBorder) {
18236         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18237                 appData.border);
18238       } else {
18239         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18240       }
18241       if(appData.useFont) {
18242         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18243                 appData.renderPiecesWithFont,
18244                 appData.fontToPieceTable,
18245                 Col2Text(9),    // appData.fontBackColorWhite
18246                 Col2Text(10) ); // appData.fontForeColorBlack
18247       } else {
18248         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18249                 appData.pieceDirectory);
18250         if(!appData.pieceDirectory[0])
18251           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18252                 Col2Text(0),   // whitePieceColor
18253                 Col2Text(1) ); // blackPieceColor
18254       }
18255       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18256                 Col2Text(4),   // highlightSquareColor
18257                 Col2Text(5) ); // premoveHighlightColor
18258         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18259         if(insert != q) insert[-1] = NULLCHAR;
18260         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18261         if(q)   free(q);
18262     }
18263     ActivateTheme(FALSE);
18264 }