Automatically switch to variant engine supports
[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 != VariantShogi)
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 != VariantShogi)
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     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10020         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10021     } else
10022     if(promoChar == '+') {
10023         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10024         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10025         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10026           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10027     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10028         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10029         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10030            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10031         board[toY][toX] = newPiece;
10032     }
10033     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10034                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10035         // [HGM] superchess: take promotion piece out of holdings
10036         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10037         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10038             if(!--board[k][BOARD_WIDTH-2])
10039                 board[k][BOARD_WIDTH-1] = EmptySquare;
10040         } else {
10041             if(!--board[BOARD_HEIGHT-1-k][1])
10042                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10043         }
10044     }
10045 }
10046
10047 /* Updates forwardMostMove */
10048 void
10049 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10050 {
10051     int x = toX, y = toY;
10052     char *s = parseList[forwardMostMove];
10053     ChessSquare p = boards[forwardMostMove][toY][toX];
10054 //    forwardMostMove++; // [HGM] bare: moved downstream
10055
10056     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10057     (void) CoordsToAlgebraic(boards[forwardMostMove],
10058                              PosFlags(forwardMostMove),
10059                              fromY, fromX, y, x, promoChar,
10060                              s);
10061     if(killX >= 0 && killY >= 0)
10062         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10063
10064     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10065         int timeLeft; static int lastLoadFlag=0; int king, piece;
10066         piece = boards[forwardMostMove][fromY][fromX];
10067         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10068         if(gameInfo.variant == VariantKnightmate)
10069             king += (int) WhiteUnicorn - (int) WhiteKing;
10070         if(forwardMostMove == 0) {
10071             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10072                 fprintf(serverMoves, "%s;", UserName());
10073             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10074                 fprintf(serverMoves, "%s;", second.tidy);
10075             fprintf(serverMoves, "%s;", first.tidy);
10076             if(gameMode == MachinePlaysWhite)
10077                 fprintf(serverMoves, "%s;", UserName());
10078             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10079                 fprintf(serverMoves, "%s;", second.tidy);
10080         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10081         lastLoadFlag = loadFlag;
10082         // print base move
10083         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10084         // print castling suffix
10085         if( toY == fromY && piece == king ) {
10086             if(toX-fromX > 1)
10087                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10088             if(fromX-toX >1)
10089                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10090         }
10091         // e.p. suffix
10092         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10093              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10094              boards[forwardMostMove][toY][toX] == EmptySquare
10095              && fromX != toX && fromY != toY)
10096                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10097         // promotion suffix
10098         if(promoChar != NULLCHAR) {
10099             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10100                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10101                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10102             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10103         }
10104         if(!loadFlag) {
10105                 char buf[MOVE_LEN*2], *p; int len;
10106             fprintf(serverMoves, "/%d/%d",
10107                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10108             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10109             else                      timeLeft = blackTimeRemaining/1000;
10110             fprintf(serverMoves, "/%d", timeLeft);
10111                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10112                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10113                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10114                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10115             fprintf(serverMoves, "/%s", buf);
10116         }
10117         fflush(serverMoves);
10118     }
10119
10120     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10121         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10122       return;
10123     }
10124     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10125     if (commentList[forwardMostMove+1] != NULL) {
10126         free(commentList[forwardMostMove+1]);
10127         commentList[forwardMostMove+1] = NULL;
10128     }
10129     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10130     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10131     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10132     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10133     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10134     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10135     adjustedClock = FALSE;
10136     gameInfo.result = GameUnfinished;
10137     if (gameInfo.resultDetails != NULL) {
10138         free(gameInfo.resultDetails);
10139         gameInfo.resultDetails = NULL;
10140     }
10141     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10142                               moveList[forwardMostMove - 1]);
10143     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10144       case MT_NONE:
10145       case MT_STALEMATE:
10146       default:
10147         break;
10148       case MT_CHECK:
10149         if(gameInfo.variant != VariantShogi)
10150             strcat(parseList[forwardMostMove - 1], "+");
10151         break;
10152       case MT_CHECKMATE:
10153       case MT_STAINMATE:
10154         strcat(parseList[forwardMostMove - 1], "#");
10155         break;
10156     }
10157 }
10158
10159 /* Updates currentMove if not pausing */
10160 void
10161 ShowMove (int fromX, int fromY, int toX, int toY)
10162 {
10163     int instant = (gameMode == PlayFromGameFile) ?
10164         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10165     if(appData.noGUI) return;
10166     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10167         if (!instant) {
10168             if (forwardMostMove == currentMove + 1) {
10169                 AnimateMove(boards[forwardMostMove - 1],
10170                             fromX, fromY, toX, toY);
10171             }
10172         }
10173         currentMove = forwardMostMove;
10174     }
10175
10176     killX = killY = -1; // [HGM] lion: used up
10177
10178     if (instant) return;
10179
10180     DisplayMove(currentMove - 1);
10181     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10182             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10183                 SetHighlights(fromX, fromY, toX, toY);
10184             }
10185     }
10186     DrawPosition(FALSE, boards[currentMove]);
10187     DisplayBothClocks();
10188     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10189 }
10190
10191 void
10192 SendEgtPath (ChessProgramState *cps)
10193 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10194         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10195
10196         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10197
10198         while(*p) {
10199             char c, *q = name+1, *r, *s;
10200
10201             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10202             while(*p && *p != ',') *q++ = *p++;
10203             *q++ = ':'; *q = 0;
10204             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10205                 strcmp(name, ",nalimov:") == 0 ) {
10206                 // take nalimov path from the menu-changeable option first, if it is defined
10207               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10208                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10209             } else
10210             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10211                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10212                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10213                 s = r = StrStr(s, ":") + 1; // beginning of path info
10214                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10215                 c = *r; *r = 0;             // temporarily null-terminate path info
10216                     *--q = 0;               // strip of trailig ':' from name
10217                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10218                 *r = c;
10219                 SendToProgram(buf,cps);     // send egtbpath command for this format
10220             }
10221             if(*p == ',') p++; // read away comma to position for next format name
10222         }
10223 }
10224
10225 static int
10226 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10227 {
10228       int width = 8, height = 8, holdings = 0;             // most common sizes
10229       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10230       // correct the deviations default for each variant
10231       if( v == VariantXiangqi ) width = 9,  height = 10;
10232       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10233       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10234       if( v == VariantCapablanca || v == VariantCapaRandom ||
10235           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10236                                 width = 10;
10237       if( v == VariantCourier ) width = 12;
10238       if( v == VariantSuper )                            holdings = 8;
10239       if( v == VariantGreat )   width = 10,              holdings = 8;
10240       if( v == VariantSChess )                           holdings = 7;
10241       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10242       if( v == VariantChuChess) width = 10, height = 10;
10243       if( v == VariantChu )     width = 12, height = 12;
10244       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10245              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10246              holdingsSize >= 0 && holdingsSize != holdings;
10247 }
10248
10249 char variantError[MSG_SIZ];
10250
10251 char *
10252 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10253 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10254       char *p, *variant = VariantName(v);
10255       static char b[MSG_SIZ];
10256       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10257            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10258                                                holdingsSize, variant); // cook up sized variant name
10259            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10260            if(StrStr(list, b) == NULL) {
10261                // specific sized variant not known, check if general sizing allowed
10262                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10263                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10264                             boardWidth, boardHeight, holdingsSize, engine);
10265                    return NULL;
10266                }
10267                /* [HGM] here we really should compare with the maximum supported board size */
10268            }
10269       } else snprintf(b, MSG_SIZ,"%s", variant);
10270       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10271       p = StrStr(list, b);
10272       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10273       if(p == NULL) {
10274           // occurs not at all in list, or only as sub-string
10275           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10276           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10277               int l = strlen(variantError);
10278               char *q;
10279               while(p != list && p[-1] != ',') p--;
10280               q = strchr(p, ',');
10281               if(q) *q = NULLCHAR;
10282               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10283               if(q) *q= ',';
10284           }
10285           return NULL;
10286       }
10287       return b;
10288 }
10289
10290 void
10291 InitChessProgram (ChessProgramState *cps, int setup)
10292 /* setup needed to setup FRC opening position */
10293 {
10294     char buf[MSG_SIZ], *b;
10295     if (appData.noChessProgram) return;
10296     hintRequested = FALSE;
10297     bookRequested = FALSE;
10298
10299     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10300     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10301     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10302     if(cps->memSize) { /* [HGM] memory */
10303       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10304         SendToProgram(buf, cps);
10305     }
10306     SendEgtPath(cps); /* [HGM] EGT */
10307     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10308       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10309         SendToProgram(buf, cps);
10310     }
10311
10312     SendToProgram(cps->initString, cps);
10313     if (gameInfo.variant != VariantNormal &&
10314         gameInfo.variant != VariantLoadable
10315         /* [HGM] also send variant if board size non-standard */
10316         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10317
10318       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10319                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10320       if (b == NULL) {
10321         DisplayFatalError(variantError, 0, 1);
10322         return;
10323       }
10324
10325       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10326       SendToProgram(buf, cps);
10327     }
10328     currentlyInitializedVariant = gameInfo.variant;
10329
10330     /* [HGM] send opening position in FRC to first engine */
10331     if(setup) {
10332           SendToProgram("force\n", cps);
10333           SendBoard(cps, 0);
10334           /* engine is now in force mode! Set flag to wake it up after first move. */
10335           setboardSpoiledMachineBlack = 1;
10336     }
10337
10338     if (cps->sendICS) {
10339       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10340       SendToProgram(buf, cps);
10341     }
10342     cps->maybeThinking = FALSE;
10343     cps->offeredDraw = 0;
10344     if (!appData.icsActive) {
10345         SendTimeControl(cps, movesPerSession, timeControl,
10346                         timeIncrement, appData.searchDepth,
10347                         searchTime);
10348     }
10349     if (appData.showThinking
10350         // [HGM] thinking: four options require thinking output to be sent
10351         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10352                                 ) {
10353         SendToProgram("post\n", cps);
10354     }
10355     SendToProgram("hard\n", cps);
10356     if (!appData.ponderNextMove) {
10357         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10358            it without being sure what state we are in first.  "hard"
10359            is not a toggle, so that one is OK.
10360          */
10361         SendToProgram("easy\n", cps);
10362     }
10363     if (cps->usePing) {
10364       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10365       SendToProgram(buf, cps);
10366     }
10367     cps->initDone = TRUE;
10368     ClearEngineOutputPane(cps == &second);
10369 }
10370
10371
10372 void
10373 ResendOptions (ChessProgramState *cps)
10374 { // send the stored value of the options
10375   int i;
10376   char buf[MSG_SIZ];
10377   Option *opt = cps->option;
10378   for(i=0; i<cps->nrOptions; i++, opt++) {
10379       switch(opt->type) {
10380         case Spin:
10381         case Slider:
10382         case CheckBox:
10383             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10384           break;
10385         case ComboBox:
10386           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10387           break;
10388         default:
10389             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10390           break;
10391         case Button:
10392         case SaveButton:
10393           continue;
10394       }
10395       SendToProgram(buf, cps);
10396   }
10397 }
10398
10399 void
10400 StartChessProgram (ChessProgramState *cps)
10401 {
10402     char buf[MSG_SIZ];
10403     int err;
10404
10405     if (appData.noChessProgram) return;
10406     cps->initDone = FALSE;
10407
10408     if (strcmp(cps->host, "localhost") == 0) {
10409         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10410     } else if (*appData.remoteShell == NULLCHAR) {
10411         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10412     } else {
10413         if (*appData.remoteUser == NULLCHAR) {
10414           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10415                     cps->program);
10416         } else {
10417           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10418                     cps->host, appData.remoteUser, cps->program);
10419         }
10420         err = StartChildProcess(buf, "", &cps->pr);
10421     }
10422
10423     if (err != 0) {
10424       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10425         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10426         if(cps != &first) return;
10427         appData.noChessProgram = TRUE;
10428         ThawUI();
10429         SetNCPMode();
10430 //      DisplayFatalError(buf, err, 1);
10431 //      cps->pr = NoProc;
10432 //      cps->isr = NULL;
10433         return;
10434     }
10435
10436     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10437     if (cps->protocolVersion > 1) {
10438       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10439       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10440         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10441         cps->comboCnt = 0;  //                and values of combo boxes
10442       }
10443       SendToProgram(buf, cps);
10444       if(cps->reload) ResendOptions(cps);
10445     } else {
10446       SendToProgram("xboard\n", cps);
10447     }
10448 }
10449
10450 void
10451 TwoMachinesEventIfReady P((void))
10452 {
10453   static int curMess = 0;
10454   if (first.lastPing != first.lastPong) {
10455     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10456     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10457     return;
10458   }
10459   if (second.lastPing != second.lastPong) {
10460     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10461     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10462     return;
10463   }
10464   DisplayMessage("", ""); curMess = 0;
10465   TwoMachinesEvent();
10466 }
10467
10468 char *
10469 MakeName (char *template)
10470 {
10471     time_t clock;
10472     struct tm *tm;
10473     static char buf[MSG_SIZ];
10474     char *p = buf;
10475     int i;
10476
10477     clock = time((time_t *)NULL);
10478     tm = localtime(&clock);
10479
10480     while(*p++ = *template++) if(p[-1] == '%') {
10481         switch(*template++) {
10482           case 0:   *p = 0; return buf;
10483           case 'Y': i = tm->tm_year+1900; break;
10484           case 'y': i = tm->tm_year-100; break;
10485           case 'M': i = tm->tm_mon+1; break;
10486           case 'd': i = tm->tm_mday; break;
10487           case 'h': i = tm->tm_hour; break;
10488           case 'm': i = tm->tm_min; break;
10489           case 's': i = tm->tm_sec; break;
10490           default:  i = 0;
10491         }
10492         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10493     }
10494     return buf;
10495 }
10496
10497 int
10498 CountPlayers (char *p)
10499 {
10500     int n = 0;
10501     while(p = strchr(p, '\n')) p++, n++; // count participants
10502     return n;
10503 }
10504
10505 FILE *
10506 WriteTourneyFile (char *results, FILE *f)
10507 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10508     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10509     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10510         // create a file with tournament description
10511         fprintf(f, "-participants {%s}\n", appData.participants);
10512         fprintf(f, "-seedBase %d\n", appData.seedBase);
10513         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10514         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10515         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10516         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10517         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10518         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10519         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10520         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10521         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10522         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10523         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10524         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10525         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10526         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10527         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10528         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10529         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10530         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10531         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10532         fprintf(f, "-smpCores %d\n", appData.smpCores);
10533         if(searchTime > 0)
10534                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10535         else {
10536                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10537                 fprintf(f, "-tc %s\n", appData.timeControl);
10538                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10539         }
10540         fprintf(f, "-results \"%s\"\n", results);
10541     }
10542     return f;
10543 }
10544
10545 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10546
10547 void
10548 Substitute (char *participants, int expunge)
10549 {
10550     int i, changed, changes=0, nPlayers=0;
10551     char *p, *q, *r, buf[MSG_SIZ];
10552     if(participants == NULL) return;
10553     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10554     r = p = participants; q = appData.participants;
10555     while(*p && *p == *q) {
10556         if(*p == '\n') r = p+1, nPlayers++;
10557         p++; q++;
10558     }
10559     if(*p) { // difference
10560         while(*p && *p++ != '\n');
10561         while(*q && *q++ != '\n');
10562       changed = nPlayers;
10563         changes = 1 + (strcmp(p, q) != 0);
10564     }
10565     if(changes == 1) { // a single engine mnemonic was changed
10566         q = r; while(*q) nPlayers += (*q++ == '\n');
10567         p = buf; while(*r && (*p = *r++) != '\n') p++;
10568         *p = NULLCHAR;
10569         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10570         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10571         if(mnemonic[i]) { // The substitute is valid
10572             FILE *f;
10573             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10574                 flock(fileno(f), LOCK_EX);
10575                 ParseArgsFromFile(f);
10576                 fseek(f, 0, SEEK_SET);
10577                 FREE(appData.participants); appData.participants = participants;
10578                 if(expunge) { // erase results of replaced engine
10579                     int len = strlen(appData.results), w, b, dummy;
10580                     for(i=0; i<len; i++) {
10581                         Pairing(i, nPlayers, &w, &b, &dummy);
10582                         if((w == changed || b == changed) && appData.results[i] == '*') {
10583                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10584                             fclose(f);
10585                             return;
10586                         }
10587                     }
10588                     for(i=0; i<len; i++) {
10589                         Pairing(i, nPlayers, &w, &b, &dummy);
10590                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10591                     }
10592                 }
10593                 WriteTourneyFile(appData.results, f);
10594                 fclose(f); // release lock
10595                 return;
10596             }
10597         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10598     }
10599     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10600     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10601     free(participants);
10602     return;
10603 }
10604
10605 int
10606 CheckPlayers (char *participants)
10607 {
10608         int i;
10609         char buf[MSG_SIZ], *p;
10610         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10611         while(p = strchr(participants, '\n')) {
10612             *p = NULLCHAR;
10613             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10614             if(!mnemonic[i]) {
10615                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10616                 *p = '\n';
10617                 DisplayError(buf, 0);
10618                 return 1;
10619             }
10620             *p = '\n';
10621             participants = p + 1;
10622         }
10623         return 0;
10624 }
10625
10626 int
10627 CreateTourney (char *name)
10628 {
10629         FILE *f;
10630         if(matchMode && strcmp(name, appData.tourneyFile)) {
10631              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10632         }
10633         if(name[0] == NULLCHAR) {
10634             if(appData.participants[0])
10635                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10636             return 0;
10637         }
10638         f = fopen(name, "r");
10639         if(f) { // file exists
10640             ASSIGN(appData.tourneyFile, name);
10641             ParseArgsFromFile(f); // parse it
10642         } else {
10643             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10644             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10645                 DisplayError(_("Not enough participants"), 0);
10646                 return 0;
10647             }
10648             if(CheckPlayers(appData.participants)) return 0;
10649             ASSIGN(appData.tourneyFile, name);
10650             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10651             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10652         }
10653         fclose(f);
10654         appData.noChessProgram = FALSE;
10655         appData.clockMode = TRUE;
10656         SetGNUMode();
10657         return 1;
10658 }
10659
10660 int
10661 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10662 {
10663     char buf[MSG_SIZ], *p, *q;
10664     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10665     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10666     skip = !all && group[0]; // if group requested, we start in skip mode
10667     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10668         p = names; q = buf; header = 0;
10669         while(*p && *p != '\n') *q++ = *p++;
10670         *q = 0;
10671         if(*p == '\n') p++;
10672         if(buf[0] == '#') {
10673             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10674             depth++; // we must be entering a new group
10675             if(all) continue; // suppress printing group headers when complete list requested
10676             header = 1;
10677             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10678         }
10679         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10680         if(engineList[i]) free(engineList[i]);
10681         engineList[i] = strdup(buf);
10682         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10683         if(engineMnemonic[i]) free(engineMnemonic[i]);
10684         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10685             strcat(buf, " (");
10686             sscanf(q + 8, "%s", buf + strlen(buf));
10687             strcat(buf, ")");
10688         }
10689         engineMnemonic[i] = strdup(buf);
10690         i++;
10691     }
10692     engineList[i] = engineMnemonic[i] = NULL;
10693     return i;
10694 }
10695
10696 // following implemented as macro to avoid type limitations
10697 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10698
10699 void
10700 SwapEngines (int n)
10701 {   // swap settings for first engine and other engine (so far only some selected options)
10702     int h;
10703     char *p;
10704     if(n == 0) return;
10705     SWAP(directory, p)
10706     SWAP(chessProgram, p)
10707     SWAP(isUCI, h)
10708     SWAP(hasOwnBookUCI, h)
10709     SWAP(protocolVersion, h)
10710     SWAP(reuse, h)
10711     SWAP(scoreIsAbsolute, h)
10712     SWAP(timeOdds, h)
10713     SWAP(logo, p)
10714     SWAP(pgnName, p)
10715     SWAP(pvSAN, h)
10716     SWAP(engOptions, p)
10717     SWAP(engInitString, p)
10718     SWAP(computerString, p)
10719     SWAP(features, p)
10720     SWAP(fenOverride, p)
10721     SWAP(NPS, h)
10722     SWAP(accumulateTC, h)
10723     SWAP(host, p)
10724 }
10725
10726 int
10727 GetEngineLine (char *s, int n)
10728 {
10729     int i;
10730     char buf[MSG_SIZ];
10731     extern char *icsNames;
10732     if(!s || !*s) return 0;
10733     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10734     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10735     if(!mnemonic[i]) return 0;
10736     if(n == 11) return 1; // just testing if there was a match
10737     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10738     if(n == 1) SwapEngines(n);
10739     ParseArgsFromString(buf);
10740     if(n == 1) SwapEngines(n);
10741     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10742         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10743         ParseArgsFromString(buf);
10744     }
10745     return 1;
10746 }
10747
10748 int
10749 SetPlayer (int player, char *p)
10750 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10751     int i;
10752     char buf[MSG_SIZ], *engineName;
10753     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10754     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10755     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10756     if(mnemonic[i]) {
10757         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10758         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10759         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10760         ParseArgsFromString(buf);
10761     } else { // no engine with this nickname is installed!
10762         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10763         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10764         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10765         ModeHighlight();
10766         DisplayError(buf, 0);
10767         return 0;
10768     }
10769     free(engineName);
10770     return i;
10771 }
10772
10773 char *recentEngines;
10774
10775 void
10776 RecentEngineEvent (int nr)
10777 {
10778     int n;
10779 //    SwapEngines(1); // bump first to second
10780 //    ReplaceEngine(&second, 1); // and load it there
10781     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10782     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10783     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10784         ReplaceEngine(&first, 0);
10785         FloatToFront(&appData.recentEngineList, command[n]);
10786     }
10787 }
10788
10789 int
10790 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10791 {   // determine players from game number
10792     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10793
10794     if(appData.tourneyType == 0) {
10795         roundsPerCycle = (nPlayers - 1) | 1;
10796         pairingsPerRound = nPlayers / 2;
10797     } else if(appData.tourneyType > 0) {
10798         roundsPerCycle = nPlayers - appData.tourneyType;
10799         pairingsPerRound = appData.tourneyType;
10800     }
10801     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10802     gamesPerCycle = gamesPerRound * roundsPerCycle;
10803     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10804     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10805     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10806     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10807     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10808     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10809
10810     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10811     if(appData.roundSync) *syncInterval = gamesPerRound;
10812
10813     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10814
10815     if(appData.tourneyType == 0) {
10816         if(curPairing == (nPlayers-1)/2 ) {
10817             *whitePlayer = curRound;
10818             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10819         } else {
10820             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10821             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10822             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10823             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10824         }
10825     } else if(appData.tourneyType > 1) {
10826         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10827         *whitePlayer = curRound + appData.tourneyType;
10828     } else if(appData.tourneyType > 0) {
10829         *whitePlayer = curPairing;
10830         *blackPlayer = curRound + appData.tourneyType;
10831     }
10832
10833     // take care of white/black alternation per round.
10834     // For cycles and games this is already taken care of by default, derived from matchGame!
10835     return curRound & 1;
10836 }
10837
10838 int
10839 NextTourneyGame (int nr, int *swapColors)
10840 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10841     char *p, *q;
10842     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10843     FILE *tf;
10844     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10845     tf = fopen(appData.tourneyFile, "r");
10846     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10847     ParseArgsFromFile(tf); fclose(tf);
10848     InitTimeControls(); // TC might be altered from tourney file
10849
10850     nPlayers = CountPlayers(appData.participants); // count participants
10851     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10852     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10853
10854     if(syncInterval) {
10855         p = q = appData.results;
10856         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10857         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10858             DisplayMessage(_("Waiting for other game(s)"),"");
10859             waitingForGame = TRUE;
10860             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10861             return 0;
10862         }
10863         waitingForGame = FALSE;
10864     }
10865
10866     if(appData.tourneyType < 0) {
10867         if(nr>=0 && !pairingReceived) {
10868             char buf[1<<16];
10869             if(pairing.pr == NoProc) {
10870                 if(!appData.pairingEngine[0]) {
10871                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10872                     return 0;
10873                 }
10874                 StartChessProgram(&pairing); // starts the pairing engine
10875             }
10876             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10877             SendToProgram(buf, &pairing);
10878             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10879             SendToProgram(buf, &pairing);
10880             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10881         }
10882         pairingReceived = 0;                              // ... so we continue here
10883         *swapColors = 0;
10884         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10885         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10886         matchGame = 1; roundNr = nr / syncInterval + 1;
10887     }
10888
10889     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10890
10891     // redefine engines, engine dir, etc.
10892     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10893     if(first.pr == NoProc) {
10894       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10895       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10896     }
10897     if(second.pr == NoProc) {
10898       SwapEngines(1);
10899       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10900       SwapEngines(1);         // and make that valid for second engine by swapping
10901       InitEngine(&second, 1);
10902     }
10903     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10904     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10905     return OK;
10906 }
10907
10908 void
10909 NextMatchGame ()
10910 {   // performs game initialization that does not invoke engines, and then tries to start the game
10911     int res, firstWhite, swapColors = 0;
10912     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10913     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
10914         char buf[MSG_SIZ];
10915         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10916         if(strcmp(buf, currentDebugFile)) { // name has changed
10917             FILE *f = fopen(buf, "w");
10918             if(f) { // if opening the new file failed, just keep using the old one
10919                 ASSIGN(currentDebugFile, buf);
10920                 fclose(debugFP);
10921                 debugFP = f;
10922             }
10923             if(appData.serverFileName) {
10924                 if(serverFP) fclose(serverFP);
10925                 serverFP = fopen(appData.serverFileName, "w");
10926                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10927                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10928             }
10929         }
10930     }
10931     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10932     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10933     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10934     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10935     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10936     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10937     Reset(FALSE, first.pr != NoProc);
10938     res = LoadGameOrPosition(matchGame); // setup game
10939     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10940     if(!res) return; // abort when bad game/pos file
10941     TwoMachinesEvent();
10942 }
10943
10944 void
10945 UserAdjudicationEvent (int result)
10946 {
10947     ChessMove gameResult = GameIsDrawn;
10948
10949     if( result > 0 ) {
10950         gameResult = WhiteWins;
10951     }
10952     else if( result < 0 ) {
10953         gameResult = BlackWins;
10954     }
10955
10956     if( gameMode == TwoMachinesPlay ) {
10957         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10958     }
10959 }
10960
10961
10962 // [HGM] save: calculate checksum of game to make games easily identifiable
10963 int
10964 StringCheckSum (char *s)
10965 {
10966         int i = 0;
10967         if(s==NULL) return 0;
10968         while(*s) i = i*259 + *s++;
10969         return i;
10970 }
10971
10972 int
10973 GameCheckSum ()
10974 {
10975         int i, sum=0;
10976         for(i=backwardMostMove; i<forwardMostMove; i++) {
10977                 sum += pvInfoList[i].depth;
10978                 sum += StringCheckSum(parseList[i]);
10979                 sum += StringCheckSum(commentList[i]);
10980                 sum *= 261;
10981         }
10982         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10983         return sum + StringCheckSum(commentList[i]);
10984 } // end of save patch
10985
10986 void
10987 GameEnds (ChessMove result, char *resultDetails, int whosays)
10988 {
10989     GameMode nextGameMode;
10990     int isIcsGame;
10991     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10992
10993     if(endingGame) return; /* [HGM] crash: forbid recursion */
10994     endingGame = 1;
10995     if(twoBoards) { // [HGM] dual: switch back to one board
10996         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10997         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10998     }
10999     if (appData.debugMode) {
11000       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11001               result, resultDetails ? resultDetails : "(null)", whosays);
11002     }
11003
11004     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11005
11006     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11007
11008     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11009         /* If we are playing on ICS, the server decides when the
11010            game is over, but the engine can offer to draw, claim
11011            a draw, or resign.
11012          */
11013 #if ZIPPY
11014         if (appData.zippyPlay && first.initDone) {
11015             if (result == GameIsDrawn) {
11016                 /* In case draw still needs to be claimed */
11017                 SendToICS(ics_prefix);
11018                 SendToICS("draw\n");
11019             } else if (StrCaseStr(resultDetails, "resign")) {
11020                 SendToICS(ics_prefix);
11021                 SendToICS("resign\n");
11022             }
11023         }
11024 #endif
11025         endingGame = 0; /* [HGM] crash */
11026         return;
11027     }
11028
11029     /* If we're loading the game from a file, stop */
11030     if (whosays == GE_FILE) {
11031       (void) StopLoadGameTimer();
11032       gameFileFP = NULL;
11033     }
11034
11035     /* Cancel draw offers */
11036     first.offeredDraw = second.offeredDraw = 0;
11037
11038     /* If this is an ICS game, only ICS can really say it's done;
11039        if not, anyone can. */
11040     isIcsGame = (gameMode == IcsPlayingWhite ||
11041                  gameMode == IcsPlayingBlack ||
11042                  gameMode == IcsObserving    ||
11043                  gameMode == IcsExamining);
11044
11045     if (!isIcsGame || whosays == GE_ICS) {
11046         /* OK -- not an ICS game, or ICS said it was done */
11047         StopClocks();
11048         if (!isIcsGame && !appData.noChessProgram)
11049           SetUserThinkingEnables();
11050
11051         /* [HGM] if a machine claims the game end we verify this claim */
11052         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11053             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11054                 char claimer;
11055                 ChessMove trueResult = (ChessMove) -1;
11056
11057                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11058                                             first.twoMachinesColor[0] :
11059                                             second.twoMachinesColor[0] ;
11060
11061                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11062                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11063                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11064                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11065                 } else
11066                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11067                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11068                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11069                 } else
11070                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11071                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11072                 }
11073
11074                 // now verify win claims, but not in drop games, as we don't understand those yet
11075                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11076                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11077                     (result == WhiteWins && claimer == 'w' ||
11078                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11079                       if (appData.debugMode) {
11080                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11081                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11082                       }
11083                       if(result != trueResult) {
11084                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11085                               result = claimer == 'w' ? BlackWins : WhiteWins;
11086                               resultDetails = buf;
11087                       }
11088                 } else
11089                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11090                     && (forwardMostMove <= backwardMostMove ||
11091                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11092                         (claimer=='b')==(forwardMostMove&1))
11093                                                                                   ) {
11094                       /* [HGM] verify: draws that were not flagged are false claims */
11095                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11096                       result = claimer == 'w' ? BlackWins : WhiteWins;
11097                       resultDetails = buf;
11098                 }
11099                 /* (Claiming a loss is accepted no questions asked!) */
11100             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11101                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11102                 result = GameUnfinished;
11103                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11104             }
11105             /* [HGM] bare: don't allow bare King to win */
11106             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11107                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11108                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11109                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11110                && result != GameIsDrawn)
11111             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11112                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11113                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11114                         if(p >= 0 && p <= (int)WhiteKing) k++;
11115                 }
11116                 if (appData.debugMode) {
11117                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11118                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11119                 }
11120                 if(k <= 1) {
11121                         result = GameIsDrawn;
11122                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11123                         resultDetails = buf;
11124                 }
11125             }
11126         }
11127
11128
11129         if(serverMoves != NULL && !loadFlag) { char c = '=';
11130             if(result==WhiteWins) c = '+';
11131             if(result==BlackWins) c = '-';
11132             if(resultDetails != NULL)
11133                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11134         }
11135         if (resultDetails != NULL) {
11136             gameInfo.result = result;
11137             gameInfo.resultDetails = StrSave(resultDetails);
11138
11139             /* display last move only if game was not loaded from file */
11140             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11141                 DisplayMove(currentMove - 1);
11142
11143             if (forwardMostMove != 0) {
11144                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11145                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11146                                                                 ) {
11147                     if (*appData.saveGameFile != NULLCHAR) {
11148                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11149                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11150                         else
11151                         SaveGameToFile(appData.saveGameFile, TRUE);
11152                     } else if (appData.autoSaveGames) {
11153                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11154                     }
11155                     if (*appData.savePositionFile != NULLCHAR) {
11156                         SavePositionToFile(appData.savePositionFile);
11157                     }
11158                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11159                 }
11160             }
11161
11162             /* Tell program how game ended in case it is learning */
11163             /* [HGM] Moved this to after saving the PGN, just in case */
11164             /* engine died and we got here through time loss. In that */
11165             /* case we will get a fatal error writing the pipe, which */
11166             /* would otherwise lose us the PGN.                       */
11167             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11168             /* output during GameEnds should never be fatal anymore   */
11169             if (gameMode == MachinePlaysWhite ||
11170                 gameMode == MachinePlaysBlack ||
11171                 gameMode == TwoMachinesPlay ||
11172                 gameMode == IcsPlayingWhite ||
11173                 gameMode == IcsPlayingBlack ||
11174                 gameMode == BeginningOfGame) {
11175                 char buf[MSG_SIZ];
11176                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11177                         resultDetails);
11178                 if (first.pr != NoProc) {
11179                     SendToProgram(buf, &first);
11180                 }
11181                 if (second.pr != NoProc &&
11182                     gameMode == TwoMachinesPlay) {
11183                     SendToProgram(buf, &second);
11184                 }
11185             }
11186         }
11187
11188         if (appData.icsActive) {
11189             if (appData.quietPlay &&
11190                 (gameMode == IcsPlayingWhite ||
11191                  gameMode == IcsPlayingBlack)) {
11192                 SendToICS(ics_prefix);
11193                 SendToICS("set shout 1\n");
11194             }
11195             nextGameMode = IcsIdle;
11196             ics_user_moved = FALSE;
11197             /* clean up premove.  It's ugly when the game has ended and the
11198              * premove highlights are still on the board.
11199              */
11200             if (gotPremove) {
11201               gotPremove = FALSE;
11202               ClearPremoveHighlights();
11203               DrawPosition(FALSE, boards[currentMove]);
11204             }
11205             if (whosays == GE_ICS) {
11206                 switch (result) {
11207                 case WhiteWins:
11208                     if (gameMode == IcsPlayingWhite)
11209                         PlayIcsWinSound();
11210                     else if(gameMode == IcsPlayingBlack)
11211                         PlayIcsLossSound();
11212                     break;
11213                 case BlackWins:
11214                     if (gameMode == IcsPlayingBlack)
11215                         PlayIcsWinSound();
11216                     else if(gameMode == IcsPlayingWhite)
11217                         PlayIcsLossSound();
11218                     break;
11219                 case GameIsDrawn:
11220                     PlayIcsDrawSound();
11221                     break;
11222                 default:
11223                     PlayIcsUnfinishedSound();
11224                 }
11225             }
11226             if(appData.quitNext) { ExitEvent(0); return; }
11227         } else if (gameMode == EditGame ||
11228                    gameMode == PlayFromGameFile ||
11229                    gameMode == AnalyzeMode ||
11230                    gameMode == AnalyzeFile) {
11231             nextGameMode = gameMode;
11232         } else {
11233             nextGameMode = EndOfGame;
11234         }
11235         pausing = FALSE;
11236         ModeHighlight();
11237     } else {
11238         nextGameMode = gameMode;
11239     }
11240
11241     if (appData.noChessProgram) {
11242         gameMode = nextGameMode;
11243         ModeHighlight();
11244         endingGame = 0; /* [HGM] crash */
11245         return;
11246     }
11247
11248     if (first.reuse) {
11249         /* Put first chess program into idle state */
11250         if (first.pr != NoProc &&
11251             (gameMode == MachinePlaysWhite ||
11252              gameMode == MachinePlaysBlack ||
11253              gameMode == TwoMachinesPlay ||
11254              gameMode == IcsPlayingWhite ||
11255              gameMode == IcsPlayingBlack ||
11256              gameMode == BeginningOfGame)) {
11257             SendToProgram("force\n", &first);
11258             if (first.usePing) {
11259               char buf[MSG_SIZ];
11260               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11261               SendToProgram(buf, &first);
11262             }
11263         }
11264     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11265         /* Kill off first chess program */
11266         if (first.isr != NULL)
11267           RemoveInputSource(first.isr);
11268         first.isr = NULL;
11269
11270         if (first.pr != NoProc) {
11271             ExitAnalyzeMode();
11272             DoSleep( appData.delayBeforeQuit );
11273             SendToProgram("quit\n", &first);
11274             DoSleep( appData.delayAfterQuit );
11275             DestroyChildProcess(first.pr, first.useSigterm);
11276             first.reload = TRUE;
11277         }
11278         first.pr = NoProc;
11279     }
11280     if (second.reuse) {
11281         /* Put second chess program into idle state */
11282         if (second.pr != NoProc &&
11283             gameMode == TwoMachinesPlay) {
11284             SendToProgram("force\n", &second);
11285             if (second.usePing) {
11286               char buf[MSG_SIZ];
11287               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11288               SendToProgram(buf, &second);
11289             }
11290         }
11291     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11292         /* Kill off second chess program */
11293         if (second.isr != NULL)
11294           RemoveInputSource(second.isr);
11295         second.isr = NULL;
11296
11297         if (second.pr != NoProc) {
11298             DoSleep( appData.delayBeforeQuit );
11299             SendToProgram("quit\n", &second);
11300             DoSleep( appData.delayAfterQuit );
11301             DestroyChildProcess(second.pr, second.useSigterm);
11302             second.reload = TRUE;
11303         }
11304         second.pr = NoProc;
11305     }
11306
11307     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11308         char resChar = '=';
11309         switch (result) {
11310         case WhiteWins:
11311           resChar = '+';
11312           if (first.twoMachinesColor[0] == 'w') {
11313             first.matchWins++;
11314           } else {
11315             second.matchWins++;
11316           }
11317           break;
11318         case BlackWins:
11319           resChar = '-';
11320           if (first.twoMachinesColor[0] == 'b') {
11321             first.matchWins++;
11322           } else {
11323             second.matchWins++;
11324           }
11325           break;
11326         case GameUnfinished:
11327           resChar = ' ';
11328         default:
11329           break;
11330         }
11331
11332         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11333         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11334             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11335             ReserveGame(nextGame, resChar); // sets nextGame
11336             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11337             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11338         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11339
11340         if (nextGame <= appData.matchGames && !abortMatch) {
11341             gameMode = nextGameMode;
11342             matchGame = nextGame; // this will be overruled in tourney mode!
11343             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11344             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11345             endingGame = 0; /* [HGM] crash */
11346             return;
11347         } else {
11348             gameMode = nextGameMode;
11349             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11350                      first.tidy, second.tidy,
11351                      first.matchWins, second.matchWins,
11352                      appData.matchGames - (first.matchWins + second.matchWins));
11353             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11354             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11355             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11356             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11357                 first.twoMachinesColor = "black\n";
11358                 second.twoMachinesColor = "white\n";
11359             } else {
11360                 first.twoMachinesColor = "white\n";
11361                 second.twoMachinesColor = "black\n";
11362             }
11363         }
11364     }
11365     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11366         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11367       ExitAnalyzeMode();
11368     gameMode = nextGameMode;
11369     ModeHighlight();
11370     endingGame = 0;  /* [HGM] crash */
11371     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11372         if(matchMode == TRUE) { // match through command line: exit with or without popup
11373             if(ranking) {
11374                 ToNrEvent(forwardMostMove);
11375                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11376                 else ExitEvent(0);
11377             } else DisplayFatalError(buf, 0, 0);
11378         } else { // match through menu; just stop, with or without popup
11379             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11380             ModeHighlight();
11381             if(ranking){
11382                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11383             } else DisplayNote(buf);
11384       }
11385       if(ranking) free(ranking);
11386     }
11387 }
11388
11389 /* Assumes program was just initialized (initString sent).
11390    Leaves program in force mode. */
11391 void
11392 FeedMovesToProgram (ChessProgramState *cps, int upto)
11393 {
11394     int i;
11395
11396     if (appData.debugMode)
11397       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11398               startedFromSetupPosition ? "position and " : "",
11399               backwardMostMove, upto, cps->which);
11400     if(currentlyInitializedVariant != gameInfo.variant) {
11401       char buf[MSG_SIZ];
11402         // [HGM] variantswitch: make engine aware of new variant
11403         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11404                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11405                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11406         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11407         SendToProgram(buf, cps);
11408         currentlyInitializedVariant = gameInfo.variant;
11409     }
11410     SendToProgram("force\n", cps);
11411     if (startedFromSetupPosition) {
11412         SendBoard(cps, backwardMostMove);
11413     if (appData.debugMode) {
11414         fprintf(debugFP, "feedMoves\n");
11415     }
11416     }
11417     for (i = backwardMostMove; i < upto; i++) {
11418         SendMoveToProgram(i, cps);
11419     }
11420 }
11421
11422
11423 int
11424 ResurrectChessProgram ()
11425 {
11426      /* The chess program may have exited.
11427         If so, restart it and feed it all the moves made so far. */
11428     static int doInit = 0;
11429
11430     if (appData.noChessProgram) return 1;
11431
11432     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11433         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11434         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11435         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11436     } else {
11437         if (first.pr != NoProc) return 1;
11438         StartChessProgram(&first);
11439     }
11440     InitChessProgram(&first, FALSE);
11441     FeedMovesToProgram(&first, currentMove);
11442
11443     if (!first.sendTime) {
11444         /* can't tell gnuchess what its clock should read,
11445            so we bow to its notion. */
11446         ResetClocks();
11447         timeRemaining[0][currentMove] = whiteTimeRemaining;
11448         timeRemaining[1][currentMove] = blackTimeRemaining;
11449     }
11450
11451     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11452                 appData.icsEngineAnalyze) && first.analysisSupport) {
11453       SendToProgram("analyze\n", &first);
11454       first.analyzing = TRUE;
11455     }
11456     return 1;
11457 }
11458
11459 /*
11460  * Button procedures
11461  */
11462 void
11463 Reset (int redraw, int init)
11464 {
11465     int i;
11466
11467     if (appData.debugMode) {
11468         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11469                 redraw, init, gameMode);
11470     }
11471     CleanupTail(); // [HGM] vari: delete any stored variations
11472     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11473     pausing = pauseExamInvalid = FALSE;
11474     startedFromSetupPosition = blackPlaysFirst = FALSE;
11475     firstMove = TRUE;
11476     whiteFlag = blackFlag = FALSE;
11477     userOfferedDraw = FALSE;
11478     hintRequested = bookRequested = FALSE;
11479     first.maybeThinking = FALSE;
11480     second.maybeThinking = FALSE;
11481     first.bookSuspend = FALSE; // [HGM] book
11482     second.bookSuspend = FALSE;
11483     thinkOutput[0] = NULLCHAR;
11484     lastHint[0] = NULLCHAR;
11485     ClearGameInfo(&gameInfo);
11486     gameInfo.variant = StringToVariant(appData.variant);
11487     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11488     ics_user_moved = ics_clock_paused = FALSE;
11489     ics_getting_history = H_FALSE;
11490     ics_gamenum = -1;
11491     white_holding[0] = black_holding[0] = NULLCHAR;
11492     ClearProgramStats();
11493     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11494
11495     ResetFrontEnd();
11496     ClearHighlights();
11497     flipView = appData.flipView;
11498     ClearPremoveHighlights();
11499     gotPremove = FALSE;
11500     alarmSounded = FALSE;
11501     killX = killY = -1; // [HGM] lion
11502
11503     GameEnds(EndOfFile, NULL, GE_PLAYER);
11504     if(appData.serverMovesName != NULL) {
11505         /* [HGM] prepare to make moves file for broadcasting */
11506         clock_t t = clock();
11507         if(serverMoves != NULL) fclose(serverMoves);
11508         serverMoves = fopen(appData.serverMovesName, "r");
11509         if(serverMoves != NULL) {
11510             fclose(serverMoves);
11511             /* delay 15 sec before overwriting, so all clients can see end */
11512             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11513         }
11514         serverMoves = fopen(appData.serverMovesName, "w");
11515     }
11516
11517     ExitAnalyzeMode();
11518     gameMode = BeginningOfGame;
11519     ModeHighlight();
11520     if(appData.icsActive) gameInfo.variant = VariantNormal;
11521     currentMove = forwardMostMove = backwardMostMove = 0;
11522     MarkTargetSquares(1);
11523     InitPosition(redraw);
11524     for (i = 0; i < MAX_MOVES; i++) {
11525         if (commentList[i] != NULL) {
11526             free(commentList[i]);
11527             commentList[i] = NULL;
11528         }
11529     }
11530     ResetClocks();
11531     timeRemaining[0][0] = whiteTimeRemaining;
11532     timeRemaining[1][0] = blackTimeRemaining;
11533
11534     if (first.pr == NoProc) {
11535         StartChessProgram(&first);
11536     }
11537     if (init) {
11538             InitChessProgram(&first, startedFromSetupPosition);
11539     }
11540     DisplayTitle("");
11541     DisplayMessage("", "");
11542     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11543     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11544     ClearMap();        // [HGM] exclude: invalidate map
11545 }
11546
11547 void
11548 AutoPlayGameLoop ()
11549 {
11550     for (;;) {
11551         if (!AutoPlayOneMove())
11552           return;
11553         if (matchMode || appData.timeDelay == 0)
11554           continue;
11555         if (appData.timeDelay < 0)
11556           return;
11557         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11558         break;
11559     }
11560 }
11561
11562 void
11563 AnalyzeNextGame()
11564 {
11565     ReloadGame(1); // next game
11566 }
11567
11568 int
11569 AutoPlayOneMove ()
11570 {
11571     int fromX, fromY, toX, toY;
11572
11573     if (appData.debugMode) {
11574       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11575     }
11576
11577     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11578       return FALSE;
11579
11580     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11581       pvInfoList[currentMove].depth = programStats.depth;
11582       pvInfoList[currentMove].score = programStats.score;
11583       pvInfoList[currentMove].time  = 0;
11584       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11585       else { // append analysis of final position as comment
11586         char buf[MSG_SIZ];
11587         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11588         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11589       }
11590       programStats.depth = 0;
11591     }
11592
11593     if (currentMove >= forwardMostMove) {
11594       if(gameMode == AnalyzeFile) {
11595           if(appData.loadGameIndex == -1) {
11596             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11597           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11598           } else {
11599           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11600         }
11601       }
11602 //      gameMode = EndOfGame;
11603 //      ModeHighlight();
11604
11605       /* [AS] Clear current move marker at the end of a game */
11606       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11607
11608       return FALSE;
11609     }
11610
11611     toX = moveList[currentMove][2] - AAA;
11612     toY = moveList[currentMove][3] - ONE;
11613
11614     if (moveList[currentMove][1] == '@') {
11615         if (appData.highlightLastMove) {
11616             SetHighlights(-1, -1, toX, toY);
11617         }
11618     } else {
11619         fromX = moveList[currentMove][0] - AAA;
11620         fromY = moveList[currentMove][1] - ONE;
11621
11622         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11623
11624         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11625
11626         if (appData.highlightLastMove) {
11627             SetHighlights(fromX, fromY, toX, toY);
11628         }
11629     }
11630     DisplayMove(currentMove);
11631     SendMoveToProgram(currentMove++, &first);
11632     DisplayBothClocks();
11633     DrawPosition(FALSE, boards[currentMove]);
11634     // [HGM] PV info: always display, routine tests if empty
11635     DisplayComment(currentMove - 1, commentList[currentMove]);
11636     return TRUE;
11637 }
11638
11639
11640 int
11641 LoadGameOneMove (ChessMove readAhead)
11642 {
11643     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11644     char promoChar = NULLCHAR;
11645     ChessMove moveType;
11646     char move[MSG_SIZ];
11647     char *p, *q;
11648
11649     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11650         gameMode != AnalyzeMode && gameMode != Training) {
11651         gameFileFP = NULL;
11652         return FALSE;
11653     }
11654
11655     yyboardindex = forwardMostMove;
11656     if (readAhead != EndOfFile) {
11657       moveType = readAhead;
11658     } else {
11659       if (gameFileFP == NULL)
11660           return FALSE;
11661       moveType = (ChessMove) Myylex();
11662     }
11663
11664     done = FALSE;
11665     switch (moveType) {
11666       case Comment:
11667         if (appData.debugMode)
11668           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11669         p = yy_text;
11670
11671         /* append the comment but don't display it */
11672         AppendComment(currentMove, p, FALSE);
11673         return TRUE;
11674
11675       case WhiteCapturesEnPassant:
11676       case BlackCapturesEnPassant:
11677       case WhitePromotion:
11678       case BlackPromotion:
11679       case WhiteNonPromotion:
11680       case BlackNonPromotion:
11681       case NormalMove:
11682       case FirstLeg:
11683       case WhiteKingSideCastle:
11684       case WhiteQueenSideCastle:
11685       case BlackKingSideCastle:
11686       case BlackQueenSideCastle:
11687       case WhiteKingSideCastleWild:
11688       case WhiteQueenSideCastleWild:
11689       case BlackKingSideCastleWild:
11690       case BlackQueenSideCastleWild:
11691       /* PUSH Fabien */
11692       case WhiteHSideCastleFR:
11693       case WhiteASideCastleFR:
11694       case BlackHSideCastleFR:
11695       case BlackASideCastleFR:
11696       /* POP Fabien */
11697         if (appData.debugMode)
11698           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11699         fromX = currentMoveString[0] - AAA;
11700         fromY = currentMoveString[1] - ONE;
11701         toX = currentMoveString[2] - AAA;
11702         toY = currentMoveString[3] - ONE;
11703         promoChar = currentMoveString[4];
11704         if(promoChar == ';') promoChar = NULLCHAR;
11705         break;
11706
11707       case WhiteDrop:
11708       case BlackDrop:
11709         if (appData.debugMode)
11710           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11711         fromX = moveType == WhiteDrop ?
11712           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11713         (int) CharToPiece(ToLower(currentMoveString[0]));
11714         fromY = DROP_RANK;
11715         toX = currentMoveString[2] - AAA;
11716         toY = currentMoveString[3] - ONE;
11717         break;
11718
11719       case WhiteWins:
11720       case BlackWins:
11721       case GameIsDrawn:
11722       case GameUnfinished:
11723         if (appData.debugMode)
11724           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11725         p = strchr(yy_text, '{');
11726         if (p == NULL) p = strchr(yy_text, '(');
11727         if (p == NULL) {
11728             p = yy_text;
11729             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11730         } else {
11731             q = strchr(p, *p == '{' ? '}' : ')');
11732             if (q != NULL) *q = NULLCHAR;
11733             p++;
11734         }
11735         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11736         GameEnds(moveType, p, GE_FILE);
11737         done = TRUE;
11738         if (cmailMsgLoaded) {
11739             ClearHighlights();
11740             flipView = WhiteOnMove(currentMove);
11741             if (moveType == GameUnfinished) flipView = !flipView;
11742             if (appData.debugMode)
11743               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11744         }
11745         break;
11746
11747       case EndOfFile:
11748         if (appData.debugMode)
11749           fprintf(debugFP, "Parser hit end of file\n");
11750         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11751           case MT_NONE:
11752           case MT_CHECK:
11753             break;
11754           case MT_CHECKMATE:
11755           case MT_STAINMATE:
11756             if (WhiteOnMove(currentMove)) {
11757                 GameEnds(BlackWins, "Black mates", GE_FILE);
11758             } else {
11759                 GameEnds(WhiteWins, "White mates", GE_FILE);
11760             }
11761             break;
11762           case MT_STALEMATE:
11763             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11764             break;
11765         }
11766         done = TRUE;
11767         break;
11768
11769       case MoveNumberOne:
11770         if (lastLoadGameStart == GNUChessGame) {
11771             /* GNUChessGames have numbers, but they aren't move numbers */
11772             if (appData.debugMode)
11773               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11774                       yy_text, (int) moveType);
11775             return LoadGameOneMove(EndOfFile); /* tail recursion */
11776         }
11777         /* else fall thru */
11778
11779       case XBoardGame:
11780       case GNUChessGame:
11781       case PGNTag:
11782         /* Reached start of next game in file */
11783         if (appData.debugMode)
11784           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11785         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11786           case MT_NONE:
11787           case MT_CHECK:
11788             break;
11789           case MT_CHECKMATE:
11790           case MT_STAINMATE:
11791             if (WhiteOnMove(currentMove)) {
11792                 GameEnds(BlackWins, "Black mates", GE_FILE);
11793             } else {
11794                 GameEnds(WhiteWins, "White mates", GE_FILE);
11795             }
11796             break;
11797           case MT_STALEMATE:
11798             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11799             break;
11800         }
11801         done = TRUE;
11802         break;
11803
11804       case PositionDiagram:     /* should not happen; ignore */
11805       case ElapsedTime:         /* ignore */
11806       case NAG:                 /* ignore */
11807         if (appData.debugMode)
11808           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11809                   yy_text, (int) moveType);
11810         return LoadGameOneMove(EndOfFile); /* tail recursion */
11811
11812       case IllegalMove:
11813         if (appData.testLegality) {
11814             if (appData.debugMode)
11815               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11816             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11817                     (forwardMostMove / 2) + 1,
11818                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11819             DisplayError(move, 0);
11820             done = TRUE;
11821         } else {
11822             if (appData.debugMode)
11823               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11824                       yy_text, currentMoveString);
11825             fromX = currentMoveString[0] - AAA;
11826             fromY = currentMoveString[1] - ONE;
11827             toX = currentMoveString[2] - AAA;
11828             toY = currentMoveString[3] - ONE;
11829             promoChar = currentMoveString[4];
11830         }
11831         break;
11832
11833       case AmbiguousMove:
11834         if (appData.debugMode)
11835           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11836         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11837                 (forwardMostMove / 2) + 1,
11838                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11839         DisplayError(move, 0);
11840         done = TRUE;
11841         break;
11842
11843       default:
11844       case ImpossibleMove:
11845         if (appData.debugMode)
11846           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11847         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11848                 (forwardMostMove / 2) + 1,
11849                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11850         DisplayError(move, 0);
11851         done = TRUE;
11852         break;
11853     }
11854
11855     if (done) {
11856         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11857             DrawPosition(FALSE, boards[currentMove]);
11858             DisplayBothClocks();
11859             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11860               DisplayComment(currentMove - 1, commentList[currentMove]);
11861         }
11862         (void) StopLoadGameTimer();
11863         gameFileFP = NULL;
11864         cmailOldMove = forwardMostMove;
11865         return FALSE;
11866     } else {
11867         /* currentMoveString is set as a side-effect of yylex */
11868
11869         thinkOutput[0] = NULLCHAR;
11870         MakeMove(fromX, fromY, toX, toY, promoChar);
11871         killX = killY = -1; // [HGM] lion: used up
11872         currentMove = forwardMostMove;
11873         return TRUE;
11874     }
11875 }
11876
11877 /* Load the nth game from the given file */
11878 int
11879 LoadGameFromFile (char *filename, int n, char *title, int useList)
11880 {
11881     FILE *f;
11882     char buf[MSG_SIZ];
11883
11884     if (strcmp(filename, "-") == 0) {
11885         f = stdin;
11886         title = "stdin";
11887     } else {
11888         f = fopen(filename, "rb");
11889         if (f == NULL) {
11890           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11891             DisplayError(buf, errno);
11892             return FALSE;
11893         }
11894     }
11895     if (fseek(f, 0, 0) == -1) {
11896         /* f is not seekable; probably a pipe */
11897         useList = FALSE;
11898     }
11899     if (useList && n == 0) {
11900         int error = GameListBuild(f);
11901         if (error) {
11902             DisplayError(_("Cannot build game list"), error);
11903         } else if (!ListEmpty(&gameList) &&
11904                    ((ListGame *) gameList.tailPred)->number > 1) {
11905             GameListPopUp(f, title);
11906             return TRUE;
11907         }
11908         GameListDestroy();
11909         n = 1;
11910     }
11911     if (n == 0) n = 1;
11912     return LoadGame(f, n, title, FALSE);
11913 }
11914
11915
11916 void
11917 MakeRegisteredMove ()
11918 {
11919     int fromX, fromY, toX, toY;
11920     char promoChar;
11921     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11922         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11923           case CMAIL_MOVE:
11924           case CMAIL_DRAW:
11925             if (appData.debugMode)
11926               fprintf(debugFP, "Restoring %s for game %d\n",
11927                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11928
11929             thinkOutput[0] = NULLCHAR;
11930             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11931             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11932             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11933             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11934             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11935             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11936             MakeMove(fromX, fromY, toX, toY, promoChar);
11937             ShowMove(fromX, fromY, toX, toY);
11938
11939             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11940               case MT_NONE:
11941               case MT_CHECK:
11942                 break;
11943
11944               case MT_CHECKMATE:
11945               case MT_STAINMATE:
11946                 if (WhiteOnMove(currentMove)) {
11947                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11948                 } else {
11949                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11950                 }
11951                 break;
11952
11953               case MT_STALEMATE:
11954                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11955                 break;
11956             }
11957
11958             break;
11959
11960           case CMAIL_RESIGN:
11961             if (WhiteOnMove(currentMove)) {
11962                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11963             } else {
11964                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11965             }
11966             break;
11967
11968           case CMAIL_ACCEPT:
11969             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11970             break;
11971
11972           default:
11973             break;
11974         }
11975     }
11976
11977     return;
11978 }
11979
11980 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11981 int
11982 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11983 {
11984     int retVal;
11985
11986     if (gameNumber > nCmailGames) {
11987         DisplayError(_("No more games in this message"), 0);
11988         return FALSE;
11989     }
11990     if (f == lastLoadGameFP) {
11991         int offset = gameNumber - lastLoadGameNumber;
11992         if (offset == 0) {
11993             cmailMsg[0] = NULLCHAR;
11994             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11995                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11996                 nCmailMovesRegistered--;
11997             }
11998             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11999             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12000                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12001             }
12002         } else {
12003             if (! RegisterMove()) return FALSE;
12004         }
12005     }
12006
12007     retVal = LoadGame(f, gameNumber, title, useList);
12008
12009     /* Make move registered during previous look at this game, if any */
12010     MakeRegisteredMove();
12011
12012     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12013         commentList[currentMove]
12014           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12015         DisplayComment(currentMove - 1, commentList[currentMove]);
12016     }
12017
12018     return retVal;
12019 }
12020
12021 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12022 int
12023 ReloadGame (int offset)
12024 {
12025     int gameNumber = lastLoadGameNumber + offset;
12026     if (lastLoadGameFP == NULL) {
12027         DisplayError(_("No game has been loaded yet"), 0);
12028         return FALSE;
12029     }
12030     if (gameNumber <= 0) {
12031         DisplayError(_("Can't back up any further"), 0);
12032         return FALSE;
12033     }
12034     if (cmailMsgLoaded) {
12035         return CmailLoadGame(lastLoadGameFP, gameNumber,
12036                              lastLoadGameTitle, lastLoadGameUseList);
12037     } else {
12038         return LoadGame(lastLoadGameFP, gameNumber,
12039                         lastLoadGameTitle, lastLoadGameUseList);
12040     }
12041 }
12042
12043 int keys[EmptySquare+1];
12044
12045 int
12046 PositionMatches (Board b1, Board b2)
12047 {
12048     int r, f, sum=0;
12049     switch(appData.searchMode) {
12050         case 1: return CompareWithRights(b1, b2);
12051         case 2:
12052             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12053                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12054             }
12055             return TRUE;
12056         case 3:
12057             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12058               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12059                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12060             }
12061             return sum==0;
12062         case 4:
12063             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12064                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12065             }
12066             return sum==0;
12067     }
12068     return TRUE;
12069 }
12070
12071 #define Q_PROMO  4
12072 #define Q_EP     3
12073 #define Q_BCASTL 2
12074 #define Q_WCASTL 1
12075
12076 int pieceList[256], quickBoard[256];
12077 ChessSquare pieceType[256] = { EmptySquare };
12078 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12079 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12080 int soughtTotal, turn;
12081 Boolean epOK, flipSearch;
12082
12083 typedef struct {
12084     unsigned char piece, to;
12085 } Move;
12086
12087 #define DSIZE (250000)
12088
12089 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12090 Move *moveDatabase = initialSpace;
12091 unsigned int movePtr, dataSize = DSIZE;
12092
12093 int
12094 MakePieceList (Board board, int *counts)
12095 {
12096     int r, f, n=Q_PROMO, total=0;
12097     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12098     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12099         int sq = f + (r<<4);
12100         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12101             quickBoard[sq] = ++n;
12102             pieceList[n] = sq;
12103             pieceType[n] = board[r][f];
12104             counts[board[r][f]]++;
12105             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12106             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12107             total++;
12108         }
12109     }
12110     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12111     return total;
12112 }
12113
12114 void
12115 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12116 {
12117     int sq = fromX + (fromY<<4);
12118     int piece = quickBoard[sq];
12119     quickBoard[sq] = 0;
12120     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12121     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12122         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12123         moveDatabase[movePtr++].piece = Q_WCASTL;
12124         quickBoard[sq] = piece;
12125         piece = quickBoard[from]; quickBoard[from] = 0;
12126         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12127     } else
12128     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12129         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12130         moveDatabase[movePtr++].piece = Q_BCASTL;
12131         quickBoard[sq] = piece;
12132         piece = quickBoard[from]; quickBoard[from] = 0;
12133         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12134     } else
12135     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12136         quickBoard[(fromY<<4)+toX] = 0;
12137         moveDatabase[movePtr].piece = Q_EP;
12138         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12139         moveDatabase[movePtr].to = sq;
12140     } else
12141     if(promoPiece != pieceType[piece]) {
12142         moveDatabase[movePtr++].piece = Q_PROMO;
12143         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12144     }
12145     moveDatabase[movePtr].piece = piece;
12146     quickBoard[sq] = piece;
12147     movePtr++;
12148 }
12149
12150 int
12151 PackGame (Board board)
12152 {
12153     Move *newSpace = NULL;
12154     moveDatabase[movePtr].piece = 0; // terminate previous game
12155     if(movePtr > dataSize) {
12156         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12157         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12158         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12159         if(newSpace) {
12160             int i;
12161             Move *p = moveDatabase, *q = newSpace;
12162             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12163             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12164             moveDatabase = newSpace;
12165         } else { // calloc failed, we must be out of memory. Too bad...
12166             dataSize = 0; // prevent calloc events for all subsequent games
12167             return 0;     // and signal this one isn't cached
12168         }
12169     }
12170     movePtr++;
12171     MakePieceList(board, counts);
12172     return movePtr;
12173 }
12174
12175 int
12176 QuickCompare (Board board, int *minCounts, int *maxCounts)
12177 {   // compare according to search mode
12178     int r, f;
12179     switch(appData.searchMode)
12180     {
12181       case 1: // exact position match
12182         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12183         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12184             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12185         }
12186         break;
12187       case 2: // can have extra material on empty squares
12188         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189             if(board[r][f] == EmptySquare) continue;
12190             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12191         }
12192         break;
12193       case 3: // material with exact Pawn structure
12194         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12195             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12196             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12197         } // fall through to material comparison
12198       case 4: // exact material
12199         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12200         break;
12201       case 6: // material range with given imbalance
12202         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12203         // fall through to range comparison
12204       case 5: // material range
12205         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12206     }
12207     return TRUE;
12208 }
12209
12210 int
12211 QuickScan (Board board, Move *move)
12212 {   // reconstruct game,and compare all positions in it
12213     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12214     do {
12215         int piece = move->piece;
12216         int to = move->to, from = pieceList[piece];
12217         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12218           if(!piece) return -1;
12219           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12220             piece = (++move)->piece;
12221             from = pieceList[piece];
12222             counts[pieceType[piece]]--;
12223             pieceType[piece] = (ChessSquare) move->to;
12224             counts[move->to]++;
12225           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12226             counts[pieceType[quickBoard[to]]]--;
12227             quickBoard[to] = 0; total--;
12228             move++;
12229             continue;
12230           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12231             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12232             from  = pieceList[piece]; // so this must be King
12233             quickBoard[from] = 0;
12234             pieceList[piece] = to;
12235             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12236             quickBoard[from] = 0; // rook
12237             quickBoard[to] = piece;
12238             to = move->to; piece = move->piece;
12239             goto aftercastle;
12240           }
12241         }
12242         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12243         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12244         quickBoard[from] = 0;
12245       aftercastle:
12246         quickBoard[to] = piece;
12247         pieceList[piece] = to;
12248         cnt++; turn ^= 3;
12249         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12250            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12251            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12252                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12253           ) {
12254             static int lastCounts[EmptySquare+1];
12255             int i;
12256             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12257             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12258         } else stretch = 0;
12259         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12260         move++;
12261     } while(1);
12262 }
12263
12264 void
12265 InitSearch ()
12266 {
12267     int r, f;
12268     flipSearch = FALSE;
12269     CopyBoard(soughtBoard, boards[currentMove]);
12270     soughtTotal = MakePieceList(soughtBoard, maxSought);
12271     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12272     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12273     CopyBoard(reverseBoard, boards[currentMove]);
12274     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12275         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12276         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12277         reverseBoard[r][f] = piece;
12278     }
12279     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12280     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12281     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12282                  || (boards[currentMove][CASTLING][2] == NoRights ||
12283                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12284                  && (boards[currentMove][CASTLING][5] == NoRights ||
12285                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12286       ) {
12287         flipSearch = TRUE;
12288         CopyBoard(flipBoard, soughtBoard);
12289         CopyBoard(rotateBoard, reverseBoard);
12290         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12291             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12292             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12293         }
12294     }
12295     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12296     if(appData.searchMode >= 5) {
12297         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12298         MakePieceList(soughtBoard, minSought);
12299         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12300     }
12301     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12302         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12303 }
12304
12305 GameInfo dummyInfo;
12306 static int creatingBook;
12307
12308 int
12309 GameContainsPosition (FILE *f, ListGame *lg)
12310 {
12311     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12312     int fromX, fromY, toX, toY;
12313     char promoChar;
12314     static int initDone=FALSE;
12315
12316     // weed out games based on numerical tag comparison
12317     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12318     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12319     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12320     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12321     if(!initDone) {
12322         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12323         initDone = TRUE;
12324     }
12325     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12326     else CopyBoard(boards[scratch], initialPosition); // default start position
12327     if(lg->moves) {
12328         turn = btm + 1;
12329         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12330         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12331     }
12332     if(btm) plyNr++;
12333     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12334     fseek(f, lg->offset, 0);
12335     yynewfile(f);
12336     while(1) {
12337         yyboardindex = scratch;
12338         quickFlag = plyNr+1;
12339         next = Myylex();
12340         quickFlag = 0;
12341         switch(next) {
12342             case PGNTag:
12343                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12344             default:
12345                 continue;
12346
12347             case XBoardGame:
12348             case GNUChessGame:
12349                 if(plyNr) return -1; // after we have seen moves, this is for new game
12350               continue;
12351
12352             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12353             case ImpossibleMove:
12354             case WhiteWins: // game ends here with these four
12355             case BlackWins:
12356             case GameIsDrawn:
12357             case GameUnfinished:
12358                 return -1;
12359
12360             case IllegalMove:
12361                 if(appData.testLegality) return -1;
12362             case WhiteCapturesEnPassant:
12363             case BlackCapturesEnPassant:
12364             case WhitePromotion:
12365             case BlackPromotion:
12366             case WhiteNonPromotion:
12367             case BlackNonPromotion:
12368             case NormalMove:
12369             case FirstLeg:
12370             case WhiteKingSideCastle:
12371             case WhiteQueenSideCastle:
12372             case BlackKingSideCastle:
12373             case BlackQueenSideCastle:
12374             case WhiteKingSideCastleWild:
12375             case WhiteQueenSideCastleWild:
12376             case BlackKingSideCastleWild:
12377             case BlackQueenSideCastleWild:
12378             case WhiteHSideCastleFR:
12379             case WhiteASideCastleFR:
12380             case BlackHSideCastleFR:
12381             case BlackASideCastleFR:
12382                 fromX = currentMoveString[0] - AAA;
12383                 fromY = currentMoveString[1] - ONE;
12384                 toX = currentMoveString[2] - AAA;
12385                 toY = currentMoveString[3] - ONE;
12386                 promoChar = currentMoveString[4];
12387                 break;
12388             case WhiteDrop:
12389             case BlackDrop:
12390                 fromX = next == WhiteDrop ?
12391                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12392                   (int) CharToPiece(ToLower(currentMoveString[0]));
12393                 fromY = DROP_RANK;
12394                 toX = currentMoveString[2] - AAA;
12395                 toY = currentMoveString[3] - ONE;
12396                 promoChar = 0;
12397                 break;
12398         }
12399         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12400         plyNr++;
12401         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12402         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12403         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12404         if(appData.findMirror) {
12405             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12406             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12407         }
12408     }
12409 }
12410
12411 /* Load the nth game from open file f */
12412 int
12413 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12414 {
12415     ChessMove cm;
12416     char buf[MSG_SIZ];
12417     int gn = gameNumber;
12418     ListGame *lg = NULL;
12419     int numPGNTags = 0;
12420     int err, pos = -1;
12421     GameMode oldGameMode;
12422     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12423
12424     if (appData.debugMode)
12425         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12426
12427     if (gameMode == Training )
12428         SetTrainingModeOff();
12429
12430     oldGameMode = gameMode;
12431     if (gameMode != BeginningOfGame) {
12432       Reset(FALSE, TRUE);
12433     }
12434     killX = killY = -1; // [HGM] lion: in case we did not Reset
12435
12436     gameFileFP = f;
12437     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12438         fclose(lastLoadGameFP);
12439     }
12440
12441     if (useList) {
12442         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12443
12444         if (lg) {
12445             fseek(f, lg->offset, 0);
12446             GameListHighlight(gameNumber);
12447             pos = lg->position;
12448             gn = 1;
12449         }
12450         else {
12451             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12452               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12453             else
12454             DisplayError(_("Game number out of range"), 0);
12455             return FALSE;
12456         }
12457     } else {
12458         GameListDestroy();
12459         if (fseek(f, 0, 0) == -1) {
12460             if (f == lastLoadGameFP ?
12461                 gameNumber == lastLoadGameNumber + 1 :
12462                 gameNumber == 1) {
12463                 gn = 1;
12464             } else {
12465                 DisplayError(_("Can't seek on game file"), 0);
12466                 return FALSE;
12467             }
12468         }
12469     }
12470     lastLoadGameFP = f;
12471     lastLoadGameNumber = gameNumber;
12472     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12473     lastLoadGameUseList = useList;
12474
12475     yynewfile(f);
12476
12477     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12478       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12479                 lg->gameInfo.black);
12480             DisplayTitle(buf);
12481     } else if (*title != NULLCHAR) {
12482         if (gameNumber > 1) {
12483           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12484             DisplayTitle(buf);
12485         } else {
12486             DisplayTitle(title);
12487         }
12488     }
12489
12490     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12491         gameMode = PlayFromGameFile;
12492         ModeHighlight();
12493     }
12494
12495     currentMove = forwardMostMove = backwardMostMove = 0;
12496     CopyBoard(boards[0], initialPosition);
12497     StopClocks();
12498
12499     /*
12500      * Skip the first gn-1 games in the file.
12501      * Also skip over anything that precedes an identifiable
12502      * start of game marker, to avoid being confused by
12503      * garbage at the start of the file.  Currently
12504      * recognized start of game markers are the move number "1",
12505      * the pattern "gnuchess .* game", the pattern
12506      * "^[#;%] [^ ]* game file", and a PGN tag block.
12507      * A game that starts with one of the latter two patterns
12508      * will also have a move number 1, possibly
12509      * following a position diagram.
12510      * 5-4-02: Let's try being more lenient and allowing a game to
12511      * start with an unnumbered move.  Does that break anything?
12512      */
12513     cm = lastLoadGameStart = EndOfFile;
12514     while (gn > 0) {
12515         yyboardindex = forwardMostMove;
12516         cm = (ChessMove) Myylex();
12517         switch (cm) {
12518           case EndOfFile:
12519             if (cmailMsgLoaded) {
12520                 nCmailGames = CMAIL_MAX_GAMES - gn;
12521             } else {
12522                 Reset(TRUE, TRUE);
12523                 DisplayError(_("Game not found in file"), 0);
12524             }
12525             return FALSE;
12526
12527           case GNUChessGame:
12528           case XBoardGame:
12529             gn--;
12530             lastLoadGameStart = cm;
12531             break;
12532
12533           case MoveNumberOne:
12534             switch (lastLoadGameStart) {
12535               case GNUChessGame:
12536               case XBoardGame:
12537               case PGNTag:
12538                 break;
12539               case MoveNumberOne:
12540               case EndOfFile:
12541                 gn--;           /* count this game */
12542                 lastLoadGameStart = cm;
12543                 break;
12544               default:
12545                 /* impossible */
12546                 break;
12547             }
12548             break;
12549
12550           case PGNTag:
12551             switch (lastLoadGameStart) {
12552               case GNUChessGame:
12553               case PGNTag:
12554               case MoveNumberOne:
12555               case EndOfFile:
12556                 gn--;           /* count this game */
12557                 lastLoadGameStart = cm;
12558                 break;
12559               case XBoardGame:
12560                 lastLoadGameStart = cm; /* game counted already */
12561                 break;
12562               default:
12563                 /* impossible */
12564                 break;
12565             }
12566             if (gn > 0) {
12567                 do {
12568                     yyboardindex = forwardMostMove;
12569                     cm = (ChessMove) Myylex();
12570                 } while (cm == PGNTag || cm == Comment);
12571             }
12572             break;
12573
12574           case WhiteWins:
12575           case BlackWins:
12576           case GameIsDrawn:
12577             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12578                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12579                     != CMAIL_OLD_RESULT) {
12580                     nCmailResults ++ ;
12581                     cmailResult[  CMAIL_MAX_GAMES
12582                                 - gn - 1] = CMAIL_OLD_RESULT;
12583                 }
12584             }
12585             break;
12586
12587           case NormalMove:
12588           case FirstLeg:
12589             /* Only a NormalMove can be at the start of a game
12590              * without a position diagram. */
12591             if (lastLoadGameStart == EndOfFile ) {
12592               gn--;
12593               lastLoadGameStart = MoveNumberOne;
12594             }
12595             break;
12596
12597           default:
12598             break;
12599         }
12600     }
12601
12602     if (appData.debugMode)
12603       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12604
12605     if (cm == XBoardGame) {
12606         /* Skip any header junk before position diagram and/or move 1 */
12607         for (;;) {
12608             yyboardindex = forwardMostMove;
12609             cm = (ChessMove) Myylex();
12610
12611             if (cm == EndOfFile ||
12612                 cm == GNUChessGame || cm == XBoardGame) {
12613                 /* Empty game; pretend end-of-file and handle later */
12614                 cm = EndOfFile;
12615                 break;
12616             }
12617
12618             if (cm == MoveNumberOne || cm == PositionDiagram ||
12619                 cm == PGNTag || cm == Comment)
12620               break;
12621         }
12622     } else if (cm == GNUChessGame) {
12623         if (gameInfo.event != NULL) {
12624             free(gameInfo.event);
12625         }
12626         gameInfo.event = StrSave(yy_text);
12627     }
12628
12629     startedFromSetupPosition = FALSE;
12630     while (cm == PGNTag) {
12631         if (appData.debugMode)
12632           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12633         err = ParsePGNTag(yy_text, &gameInfo);
12634         if (!err) numPGNTags++;
12635
12636         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12637         if(gameInfo.variant != oldVariant) {
12638             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12639             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12640             InitPosition(TRUE);
12641             oldVariant = gameInfo.variant;
12642             if (appData.debugMode)
12643               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12644         }
12645
12646
12647         if (gameInfo.fen != NULL) {
12648           Board initial_position;
12649           startedFromSetupPosition = TRUE;
12650           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12651             Reset(TRUE, TRUE);
12652             DisplayError(_("Bad FEN position in file"), 0);
12653             return FALSE;
12654           }
12655           CopyBoard(boards[0], initial_position);
12656           if (blackPlaysFirst) {
12657             currentMove = forwardMostMove = backwardMostMove = 1;
12658             CopyBoard(boards[1], initial_position);
12659             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12660             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12661             timeRemaining[0][1] = whiteTimeRemaining;
12662             timeRemaining[1][1] = blackTimeRemaining;
12663             if (commentList[0] != NULL) {
12664               commentList[1] = commentList[0];
12665               commentList[0] = NULL;
12666             }
12667           } else {
12668             currentMove = forwardMostMove = backwardMostMove = 0;
12669           }
12670           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12671           {   int i;
12672               initialRulePlies = FENrulePlies;
12673               for( i=0; i< nrCastlingRights; i++ )
12674                   initialRights[i] = initial_position[CASTLING][i];
12675           }
12676           yyboardindex = forwardMostMove;
12677           free(gameInfo.fen);
12678           gameInfo.fen = NULL;
12679         }
12680
12681         yyboardindex = forwardMostMove;
12682         cm = (ChessMove) Myylex();
12683
12684         /* Handle comments interspersed among the tags */
12685         while (cm == Comment) {
12686             char *p;
12687             if (appData.debugMode)
12688               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12689             p = yy_text;
12690             AppendComment(currentMove, p, FALSE);
12691             yyboardindex = forwardMostMove;
12692             cm = (ChessMove) Myylex();
12693         }
12694     }
12695
12696     /* don't rely on existence of Event tag since if game was
12697      * pasted from clipboard the Event tag may not exist
12698      */
12699     if (numPGNTags > 0){
12700         char *tags;
12701         if (gameInfo.variant == VariantNormal) {
12702           VariantClass v = StringToVariant(gameInfo.event);
12703           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12704           if(v < VariantShogi) gameInfo.variant = v;
12705         }
12706         if (!matchMode) {
12707           if( appData.autoDisplayTags ) {
12708             tags = PGNTags(&gameInfo);
12709             TagsPopUp(tags, CmailMsg());
12710             free(tags);
12711           }
12712         }
12713     } else {
12714         /* Make something up, but don't display it now */
12715         SetGameInfo();
12716         TagsPopDown();
12717     }
12718
12719     if (cm == PositionDiagram) {
12720         int i, j;
12721         char *p;
12722         Board initial_position;
12723
12724         if (appData.debugMode)
12725           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12726
12727         if (!startedFromSetupPosition) {
12728             p = yy_text;
12729             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12730               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12731                 switch (*p) {
12732                   case '{':
12733                   case '[':
12734                   case '-':
12735                   case ' ':
12736                   case '\t':
12737                   case '\n':
12738                   case '\r':
12739                     break;
12740                   default:
12741                     initial_position[i][j++] = CharToPiece(*p);
12742                     break;
12743                 }
12744             while (*p == ' ' || *p == '\t' ||
12745                    *p == '\n' || *p == '\r') p++;
12746
12747             if (strncmp(p, "black", strlen("black"))==0)
12748               blackPlaysFirst = TRUE;
12749             else
12750               blackPlaysFirst = FALSE;
12751             startedFromSetupPosition = TRUE;
12752
12753             CopyBoard(boards[0], initial_position);
12754             if (blackPlaysFirst) {
12755                 currentMove = forwardMostMove = backwardMostMove = 1;
12756                 CopyBoard(boards[1], initial_position);
12757                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12758                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12759                 timeRemaining[0][1] = whiteTimeRemaining;
12760                 timeRemaining[1][1] = blackTimeRemaining;
12761                 if (commentList[0] != NULL) {
12762                     commentList[1] = commentList[0];
12763                     commentList[0] = NULL;
12764                 }
12765             } else {
12766                 currentMove = forwardMostMove = backwardMostMove = 0;
12767             }
12768         }
12769         yyboardindex = forwardMostMove;
12770         cm = (ChessMove) Myylex();
12771     }
12772
12773   if(!creatingBook) {
12774     if (first.pr == NoProc) {
12775         StartChessProgram(&first);
12776     }
12777     InitChessProgram(&first, FALSE);
12778     SendToProgram("force\n", &first);
12779     if (startedFromSetupPosition) {
12780         SendBoard(&first, forwardMostMove);
12781     if (appData.debugMode) {
12782         fprintf(debugFP, "Load Game\n");
12783     }
12784         DisplayBothClocks();
12785     }
12786   }
12787
12788     /* [HGM] server: flag to write setup moves in broadcast file as one */
12789     loadFlag = appData.suppressLoadMoves;
12790
12791     while (cm == Comment) {
12792         char *p;
12793         if (appData.debugMode)
12794           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12795         p = yy_text;
12796         AppendComment(currentMove, p, FALSE);
12797         yyboardindex = forwardMostMove;
12798         cm = (ChessMove) Myylex();
12799     }
12800
12801     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12802         cm == WhiteWins || cm == BlackWins ||
12803         cm == GameIsDrawn || cm == GameUnfinished) {
12804         DisplayMessage("", _("No moves in game"));
12805         if (cmailMsgLoaded) {
12806             if (appData.debugMode)
12807               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12808             ClearHighlights();
12809             flipView = FALSE;
12810         }
12811         DrawPosition(FALSE, boards[currentMove]);
12812         DisplayBothClocks();
12813         gameMode = EditGame;
12814         ModeHighlight();
12815         gameFileFP = NULL;
12816         cmailOldMove = 0;
12817         return TRUE;
12818     }
12819
12820     // [HGM] PV info: routine tests if comment empty
12821     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12822         DisplayComment(currentMove - 1, commentList[currentMove]);
12823     }
12824     if (!matchMode && appData.timeDelay != 0)
12825       DrawPosition(FALSE, boards[currentMove]);
12826
12827     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12828       programStats.ok_to_send = 1;
12829     }
12830
12831     /* if the first token after the PGN tags is a move
12832      * and not move number 1, retrieve it from the parser
12833      */
12834     if (cm != MoveNumberOne)
12835         LoadGameOneMove(cm);
12836
12837     /* load the remaining moves from the file */
12838     while (LoadGameOneMove(EndOfFile)) {
12839       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12840       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12841     }
12842
12843     /* rewind to the start of the game */
12844     currentMove = backwardMostMove;
12845
12846     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12847
12848     if (oldGameMode == AnalyzeFile) {
12849       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12850       AnalyzeFileEvent();
12851     } else
12852     if (oldGameMode == AnalyzeMode) {
12853       AnalyzeFileEvent();
12854     }
12855
12856     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12857         long int w, b; // [HGM] adjourn: restore saved clock times
12858         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12859         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12860             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12861             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12862         }
12863     }
12864
12865     if(creatingBook) return TRUE;
12866     if (!matchMode && pos > 0) {
12867         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12868     } else
12869     if (matchMode || appData.timeDelay == 0) {
12870       ToEndEvent();
12871     } else if (appData.timeDelay > 0) {
12872       AutoPlayGameLoop();
12873     }
12874
12875     if (appData.debugMode)
12876         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12877
12878     loadFlag = 0; /* [HGM] true game starts */
12879     return TRUE;
12880 }
12881
12882 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12883 int
12884 ReloadPosition (int offset)
12885 {
12886     int positionNumber = lastLoadPositionNumber + offset;
12887     if (lastLoadPositionFP == NULL) {
12888         DisplayError(_("No position has been loaded yet"), 0);
12889         return FALSE;
12890     }
12891     if (positionNumber <= 0) {
12892         DisplayError(_("Can't back up any further"), 0);
12893         return FALSE;
12894     }
12895     return LoadPosition(lastLoadPositionFP, positionNumber,
12896                         lastLoadPositionTitle);
12897 }
12898
12899 /* Load the nth position from the given file */
12900 int
12901 LoadPositionFromFile (char *filename, int n, char *title)
12902 {
12903     FILE *f;
12904     char buf[MSG_SIZ];
12905
12906     if (strcmp(filename, "-") == 0) {
12907         return LoadPosition(stdin, n, "stdin");
12908     } else {
12909         f = fopen(filename, "rb");
12910         if (f == NULL) {
12911             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12912             DisplayError(buf, errno);
12913             return FALSE;
12914         } else {
12915             return LoadPosition(f, n, title);
12916         }
12917     }
12918 }
12919
12920 /* Load the nth position from the given open file, and close it */
12921 int
12922 LoadPosition (FILE *f, int positionNumber, char *title)
12923 {
12924     char *p, line[MSG_SIZ];
12925     Board initial_position;
12926     int i, j, fenMode, pn;
12927
12928     if (gameMode == Training )
12929         SetTrainingModeOff();
12930
12931     if (gameMode != BeginningOfGame) {
12932         Reset(FALSE, TRUE);
12933     }
12934     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12935         fclose(lastLoadPositionFP);
12936     }
12937     if (positionNumber == 0) positionNumber = 1;
12938     lastLoadPositionFP = f;
12939     lastLoadPositionNumber = positionNumber;
12940     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12941     if (first.pr == NoProc && !appData.noChessProgram) {
12942       StartChessProgram(&first);
12943       InitChessProgram(&first, FALSE);
12944     }
12945     pn = positionNumber;
12946     if (positionNumber < 0) {
12947         /* Negative position number means to seek to that byte offset */
12948         if (fseek(f, -positionNumber, 0) == -1) {
12949             DisplayError(_("Can't seek on position file"), 0);
12950             return FALSE;
12951         };
12952         pn = 1;
12953     } else {
12954         if (fseek(f, 0, 0) == -1) {
12955             if (f == lastLoadPositionFP ?
12956                 positionNumber == lastLoadPositionNumber + 1 :
12957                 positionNumber == 1) {
12958                 pn = 1;
12959             } else {
12960                 DisplayError(_("Can't seek on position file"), 0);
12961                 return FALSE;
12962             }
12963         }
12964     }
12965     /* See if this file is FEN or old-style xboard */
12966     if (fgets(line, MSG_SIZ, f) == NULL) {
12967         DisplayError(_("Position not found in file"), 0);
12968         return FALSE;
12969     }
12970     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12971     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12972
12973     if (pn >= 2) {
12974         if (fenMode || line[0] == '#') pn--;
12975         while (pn > 0) {
12976             /* skip positions before number pn */
12977             if (fgets(line, MSG_SIZ, f) == NULL) {
12978                 Reset(TRUE, TRUE);
12979                 DisplayError(_("Position not found in file"), 0);
12980                 return FALSE;
12981             }
12982             if (fenMode || line[0] == '#') pn--;
12983         }
12984     }
12985
12986     if (fenMode) {
12987         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12988             DisplayError(_("Bad FEN position in file"), 0);
12989             return FALSE;
12990         }
12991     } else {
12992         (void) fgets(line, MSG_SIZ, f);
12993         (void) fgets(line, MSG_SIZ, f);
12994
12995         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12996             (void) fgets(line, MSG_SIZ, f);
12997             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12998                 if (*p == ' ')
12999                   continue;
13000                 initial_position[i][j++] = CharToPiece(*p);
13001             }
13002         }
13003
13004         blackPlaysFirst = FALSE;
13005         if (!feof(f)) {
13006             (void) fgets(line, MSG_SIZ, f);
13007             if (strncmp(line, "black", strlen("black"))==0)
13008               blackPlaysFirst = TRUE;
13009         }
13010     }
13011     startedFromSetupPosition = TRUE;
13012
13013     CopyBoard(boards[0], initial_position);
13014     if (blackPlaysFirst) {
13015         currentMove = forwardMostMove = backwardMostMove = 1;
13016         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13017         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13018         CopyBoard(boards[1], initial_position);
13019         DisplayMessage("", _("Black to play"));
13020     } else {
13021         currentMove = forwardMostMove = backwardMostMove = 0;
13022         DisplayMessage("", _("White to play"));
13023     }
13024     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13025     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13026         SendToProgram("force\n", &first);
13027         SendBoard(&first, forwardMostMove);
13028     }
13029     if (appData.debugMode) {
13030 int i, j;
13031   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13032   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13033         fprintf(debugFP, "Load Position\n");
13034     }
13035
13036     if (positionNumber > 1) {
13037       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13038         DisplayTitle(line);
13039     } else {
13040         DisplayTitle(title);
13041     }
13042     gameMode = EditGame;
13043     ModeHighlight();
13044     ResetClocks();
13045     timeRemaining[0][1] = whiteTimeRemaining;
13046     timeRemaining[1][1] = blackTimeRemaining;
13047     DrawPosition(FALSE, boards[currentMove]);
13048
13049     return TRUE;
13050 }
13051
13052
13053 void
13054 CopyPlayerNameIntoFileName (char **dest, char *src)
13055 {
13056     while (*src != NULLCHAR && *src != ',') {
13057         if (*src == ' ') {
13058             *(*dest)++ = '_';
13059             src++;
13060         } else {
13061             *(*dest)++ = *src++;
13062         }
13063     }
13064 }
13065
13066 char *
13067 DefaultFileName (char *ext)
13068 {
13069     static char def[MSG_SIZ];
13070     char *p;
13071
13072     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13073         p = def;
13074         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13075         *p++ = '-';
13076         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13077         *p++ = '.';
13078         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13079     } else {
13080         def[0] = NULLCHAR;
13081     }
13082     return def;
13083 }
13084
13085 /* Save the current game to the given file */
13086 int
13087 SaveGameToFile (char *filename, int append)
13088 {
13089     FILE *f;
13090     char buf[MSG_SIZ];
13091     int result, i, t,tot=0;
13092
13093     if (strcmp(filename, "-") == 0) {
13094         return SaveGame(stdout, 0, NULL);
13095     } else {
13096         for(i=0; i<10; i++) { // upto 10 tries
13097              f = fopen(filename, append ? "a" : "w");
13098              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13099              if(f || errno != 13) break;
13100              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13101              tot += t;
13102         }
13103         if (f == NULL) {
13104             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13105             DisplayError(buf, errno);
13106             return FALSE;
13107         } else {
13108             safeStrCpy(buf, lastMsg, MSG_SIZ);
13109             DisplayMessage(_("Waiting for access to save file"), "");
13110             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13111             DisplayMessage(_("Saving game"), "");
13112             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13113             result = SaveGame(f, 0, NULL);
13114             DisplayMessage(buf, "");
13115             return result;
13116         }
13117     }
13118 }
13119
13120 char *
13121 SavePart (char *str)
13122 {
13123     static char buf[MSG_SIZ];
13124     char *p;
13125
13126     p = strchr(str, ' ');
13127     if (p == NULL) return str;
13128     strncpy(buf, str, p - str);
13129     buf[p - str] = NULLCHAR;
13130     return buf;
13131 }
13132
13133 #define PGN_MAX_LINE 75
13134
13135 #define PGN_SIDE_WHITE  0
13136 #define PGN_SIDE_BLACK  1
13137
13138 static int
13139 FindFirstMoveOutOfBook (int side)
13140 {
13141     int result = -1;
13142
13143     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13144         int index = backwardMostMove;
13145         int has_book_hit = 0;
13146
13147         if( (index % 2) != side ) {
13148             index++;
13149         }
13150
13151         while( index < forwardMostMove ) {
13152             /* Check to see if engine is in book */
13153             int depth = pvInfoList[index].depth;
13154             int score = pvInfoList[index].score;
13155             int in_book = 0;
13156
13157             if( depth <= 2 ) {
13158                 in_book = 1;
13159             }
13160             else if( score == 0 && depth == 63 ) {
13161                 in_book = 1; /* Zappa */
13162             }
13163             else if( score == 2 && depth == 99 ) {
13164                 in_book = 1; /* Abrok */
13165             }
13166
13167             has_book_hit += in_book;
13168
13169             if( ! in_book ) {
13170                 result = index;
13171
13172                 break;
13173             }
13174
13175             index += 2;
13176         }
13177     }
13178
13179     return result;
13180 }
13181
13182 void
13183 GetOutOfBookInfo (char * buf)
13184 {
13185     int oob[2];
13186     int i;
13187     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13188
13189     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13190     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13191
13192     *buf = '\0';
13193
13194     if( oob[0] >= 0 || oob[1] >= 0 ) {
13195         for( i=0; i<2; i++ ) {
13196             int idx = oob[i];
13197
13198             if( idx >= 0 ) {
13199                 if( i > 0 && oob[0] >= 0 ) {
13200                     strcat( buf, "   " );
13201                 }
13202
13203                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13204                 sprintf( buf+strlen(buf), "%s%.2f",
13205                     pvInfoList[idx].score >= 0 ? "+" : "",
13206                     pvInfoList[idx].score / 100.0 );
13207             }
13208         }
13209     }
13210 }
13211
13212 /* Save game in PGN style and close the file */
13213 int
13214 SaveGamePGN (FILE *f)
13215 {
13216     int i, offset, linelen, newblock;
13217 //    char *movetext;
13218     char numtext[32];
13219     int movelen, numlen, blank;
13220     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13221
13222     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13223
13224     PrintPGNTags(f, &gameInfo);
13225
13226     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13227
13228     if (backwardMostMove > 0 || startedFromSetupPosition) {
13229         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13230         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13231         fprintf(f, "\n{--------------\n");
13232         PrintPosition(f, backwardMostMove);
13233         fprintf(f, "--------------}\n");
13234         free(fen);
13235     }
13236     else {
13237         /* [AS] Out of book annotation */
13238         if( appData.saveOutOfBookInfo ) {
13239             char buf[64];
13240
13241             GetOutOfBookInfo( buf );
13242
13243             if( buf[0] != '\0' ) {
13244                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13245             }
13246         }
13247
13248         fprintf(f, "\n");
13249     }
13250
13251     i = backwardMostMove;
13252     linelen = 0;
13253     newblock = TRUE;
13254
13255     while (i < forwardMostMove) {
13256         /* Print comments preceding this move */
13257         if (commentList[i] != NULL) {
13258             if (linelen > 0) fprintf(f, "\n");
13259             fprintf(f, "%s", commentList[i]);
13260             linelen = 0;
13261             newblock = TRUE;
13262         }
13263
13264         /* Format move number */
13265         if ((i % 2) == 0)
13266           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13267         else
13268           if (newblock)
13269             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13270           else
13271             numtext[0] = NULLCHAR;
13272
13273         numlen = strlen(numtext);
13274         newblock = FALSE;
13275
13276         /* Print move number */
13277         blank = linelen > 0 && numlen > 0;
13278         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13279             fprintf(f, "\n");
13280             linelen = 0;
13281             blank = 0;
13282         }
13283         if (blank) {
13284             fprintf(f, " ");
13285             linelen++;
13286         }
13287         fprintf(f, "%s", numtext);
13288         linelen += numlen;
13289
13290         /* Get move */
13291         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13292         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13293
13294         /* Print move */
13295         blank = linelen > 0 && movelen > 0;
13296         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13297             fprintf(f, "\n");
13298             linelen = 0;
13299             blank = 0;
13300         }
13301         if (blank) {
13302             fprintf(f, " ");
13303             linelen++;
13304         }
13305         fprintf(f, "%s", move_buffer);
13306         linelen += movelen;
13307
13308         /* [AS] Add PV info if present */
13309         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13310             /* [HGM] add time */
13311             char buf[MSG_SIZ]; int seconds;
13312
13313             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13314
13315             if( seconds <= 0)
13316               buf[0] = 0;
13317             else
13318               if( seconds < 30 )
13319                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13320               else
13321                 {
13322                   seconds = (seconds + 4)/10; // round to full seconds
13323                   if( seconds < 60 )
13324                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13325                   else
13326                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13327                 }
13328
13329             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13330                       pvInfoList[i].score >= 0 ? "+" : "",
13331                       pvInfoList[i].score / 100.0,
13332                       pvInfoList[i].depth,
13333                       buf );
13334
13335             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13336
13337             /* Print score/depth */
13338             blank = linelen > 0 && movelen > 0;
13339             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13340                 fprintf(f, "\n");
13341                 linelen = 0;
13342                 blank = 0;
13343             }
13344             if (blank) {
13345                 fprintf(f, " ");
13346                 linelen++;
13347             }
13348             fprintf(f, "%s", move_buffer);
13349             linelen += movelen;
13350         }
13351
13352         i++;
13353     }
13354
13355     /* Start a new line */
13356     if (linelen > 0) fprintf(f, "\n");
13357
13358     /* Print comments after last move */
13359     if (commentList[i] != NULL) {
13360         fprintf(f, "%s\n", commentList[i]);
13361     }
13362
13363     /* Print result */
13364     if (gameInfo.resultDetails != NULL &&
13365         gameInfo.resultDetails[0] != NULLCHAR) {
13366         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13367         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13368            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13369             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13370         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13371     } else {
13372         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13373     }
13374
13375     fclose(f);
13376     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13377     return TRUE;
13378 }
13379
13380 /* Save game in old style and close the file */
13381 int
13382 SaveGameOldStyle (FILE *f)
13383 {
13384     int i, offset;
13385     time_t tm;
13386
13387     tm = time((time_t *) NULL);
13388
13389     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13390     PrintOpponents(f);
13391
13392     if (backwardMostMove > 0 || startedFromSetupPosition) {
13393         fprintf(f, "\n[--------------\n");
13394         PrintPosition(f, backwardMostMove);
13395         fprintf(f, "--------------]\n");
13396     } else {
13397         fprintf(f, "\n");
13398     }
13399
13400     i = backwardMostMove;
13401     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13402
13403     while (i < forwardMostMove) {
13404         if (commentList[i] != NULL) {
13405             fprintf(f, "[%s]\n", commentList[i]);
13406         }
13407
13408         if ((i % 2) == 1) {
13409             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13410             i++;
13411         } else {
13412             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13413             i++;
13414             if (commentList[i] != NULL) {
13415                 fprintf(f, "\n");
13416                 continue;
13417             }
13418             if (i >= forwardMostMove) {
13419                 fprintf(f, "\n");
13420                 break;
13421             }
13422             fprintf(f, "%s\n", parseList[i]);
13423             i++;
13424         }
13425     }
13426
13427     if (commentList[i] != NULL) {
13428         fprintf(f, "[%s]\n", commentList[i]);
13429     }
13430
13431     /* This isn't really the old style, but it's close enough */
13432     if (gameInfo.resultDetails != NULL &&
13433         gameInfo.resultDetails[0] != NULLCHAR) {
13434         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13435                 gameInfo.resultDetails);
13436     } else {
13437         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13438     }
13439
13440     fclose(f);
13441     return TRUE;
13442 }
13443
13444 /* Save the current game to open file f and close the file */
13445 int
13446 SaveGame (FILE *f, int dummy, char *dummy2)
13447 {
13448     if (gameMode == EditPosition) EditPositionDone(TRUE);
13449     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13450     if (appData.oldSaveStyle)
13451       return SaveGameOldStyle(f);
13452     else
13453       return SaveGamePGN(f);
13454 }
13455
13456 /* Save the current position to the given file */
13457 int
13458 SavePositionToFile (char *filename)
13459 {
13460     FILE *f;
13461     char buf[MSG_SIZ];
13462
13463     if (strcmp(filename, "-") == 0) {
13464         return SavePosition(stdout, 0, NULL);
13465     } else {
13466         f = fopen(filename, "a");
13467         if (f == NULL) {
13468             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13469             DisplayError(buf, errno);
13470             return FALSE;
13471         } else {
13472             safeStrCpy(buf, lastMsg, MSG_SIZ);
13473             DisplayMessage(_("Waiting for access to save file"), "");
13474             flock(fileno(f), LOCK_EX); // [HGM] lock
13475             DisplayMessage(_("Saving position"), "");
13476             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13477             SavePosition(f, 0, NULL);
13478             DisplayMessage(buf, "");
13479             return TRUE;
13480         }
13481     }
13482 }
13483
13484 /* Save the current position to the given open file and close the file */
13485 int
13486 SavePosition (FILE *f, int dummy, char *dummy2)
13487 {
13488     time_t tm;
13489     char *fen;
13490
13491     if (gameMode == EditPosition) EditPositionDone(TRUE);
13492     if (appData.oldSaveStyle) {
13493         tm = time((time_t *) NULL);
13494
13495         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13496         PrintOpponents(f);
13497         fprintf(f, "[--------------\n");
13498         PrintPosition(f, currentMove);
13499         fprintf(f, "--------------]\n");
13500     } else {
13501         fen = PositionToFEN(currentMove, NULL, 1);
13502         fprintf(f, "%s\n", fen);
13503         free(fen);
13504     }
13505     fclose(f);
13506     return TRUE;
13507 }
13508
13509 void
13510 ReloadCmailMsgEvent (int unregister)
13511 {
13512 #if !WIN32
13513     static char *inFilename = NULL;
13514     static char *outFilename;
13515     int i;
13516     struct stat inbuf, outbuf;
13517     int status;
13518
13519     /* Any registered moves are unregistered if unregister is set, */
13520     /* i.e. invoked by the signal handler */
13521     if (unregister) {
13522         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13523             cmailMoveRegistered[i] = FALSE;
13524             if (cmailCommentList[i] != NULL) {
13525                 free(cmailCommentList[i]);
13526                 cmailCommentList[i] = NULL;
13527             }
13528         }
13529         nCmailMovesRegistered = 0;
13530     }
13531
13532     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13533         cmailResult[i] = CMAIL_NOT_RESULT;
13534     }
13535     nCmailResults = 0;
13536
13537     if (inFilename == NULL) {
13538         /* Because the filenames are static they only get malloced once  */
13539         /* and they never get freed                                      */
13540         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13541         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13542
13543         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13544         sprintf(outFilename, "%s.out", appData.cmailGameName);
13545     }
13546
13547     status = stat(outFilename, &outbuf);
13548     if (status < 0) {
13549         cmailMailedMove = FALSE;
13550     } else {
13551         status = stat(inFilename, &inbuf);
13552         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13553     }
13554
13555     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13556        counts the games, notes how each one terminated, etc.
13557
13558        It would be nice to remove this kludge and instead gather all
13559        the information while building the game list.  (And to keep it
13560        in the game list nodes instead of having a bunch of fixed-size
13561        parallel arrays.)  Note this will require getting each game's
13562        termination from the PGN tags, as the game list builder does
13563        not process the game moves.  --mann
13564        */
13565     cmailMsgLoaded = TRUE;
13566     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13567
13568     /* Load first game in the file or popup game menu */
13569     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13570
13571 #endif /* !WIN32 */
13572     return;
13573 }
13574
13575 int
13576 RegisterMove ()
13577 {
13578     FILE *f;
13579     char string[MSG_SIZ];
13580
13581     if (   cmailMailedMove
13582         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13583         return TRUE;            /* Allow free viewing  */
13584     }
13585
13586     /* Unregister move to ensure that we don't leave RegisterMove        */
13587     /* with the move registered when the conditions for registering no   */
13588     /* longer hold                                                       */
13589     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13590         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13591         nCmailMovesRegistered --;
13592
13593         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13594           {
13595               free(cmailCommentList[lastLoadGameNumber - 1]);
13596               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13597           }
13598     }
13599
13600     if (cmailOldMove == -1) {
13601         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13602         return FALSE;
13603     }
13604
13605     if (currentMove > cmailOldMove + 1) {
13606         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13607         return FALSE;
13608     }
13609
13610     if (currentMove < cmailOldMove) {
13611         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13612         return FALSE;
13613     }
13614
13615     if (forwardMostMove > currentMove) {
13616         /* Silently truncate extra moves */
13617         TruncateGame();
13618     }
13619
13620     if (   (currentMove == cmailOldMove + 1)
13621         || (   (currentMove == cmailOldMove)
13622             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13623                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13624         if (gameInfo.result != GameUnfinished) {
13625             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13626         }
13627
13628         if (commentList[currentMove] != NULL) {
13629             cmailCommentList[lastLoadGameNumber - 1]
13630               = StrSave(commentList[currentMove]);
13631         }
13632         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13633
13634         if (appData.debugMode)
13635           fprintf(debugFP, "Saving %s for game %d\n",
13636                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13637
13638         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13639
13640         f = fopen(string, "w");
13641         if (appData.oldSaveStyle) {
13642             SaveGameOldStyle(f); /* also closes the file */
13643
13644             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13645             f = fopen(string, "w");
13646             SavePosition(f, 0, NULL); /* also closes the file */
13647         } else {
13648             fprintf(f, "{--------------\n");
13649             PrintPosition(f, currentMove);
13650             fprintf(f, "--------------}\n\n");
13651
13652             SaveGame(f, 0, NULL); /* also closes the file*/
13653         }
13654
13655         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13656         nCmailMovesRegistered ++;
13657     } else if (nCmailGames == 1) {
13658         DisplayError(_("You have not made a move yet"), 0);
13659         return FALSE;
13660     }
13661
13662     return TRUE;
13663 }
13664
13665 void
13666 MailMoveEvent ()
13667 {
13668 #if !WIN32
13669     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13670     FILE *commandOutput;
13671     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13672     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13673     int nBuffers;
13674     int i;
13675     int archived;
13676     char *arcDir;
13677
13678     if (! cmailMsgLoaded) {
13679         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13680         return;
13681     }
13682
13683     if (nCmailGames == nCmailResults) {
13684         DisplayError(_("No unfinished games"), 0);
13685         return;
13686     }
13687
13688 #if CMAIL_PROHIBIT_REMAIL
13689     if (cmailMailedMove) {
13690       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);
13691         DisplayError(msg, 0);
13692         return;
13693     }
13694 #endif
13695
13696     if (! (cmailMailedMove || RegisterMove())) return;
13697
13698     if (   cmailMailedMove
13699         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13700       snprintf(string, MSG_SIZ, partCommandString,
13701                appData.debugMode ? " -v" : "", appData.cmailGameName);
13702         commandOutput = popen(string, "r");
13703
13704         if (commandOutput == NULL) {
13705             DisplayError(_("Failed to invoke cmail"), 0);
13706         } else {
13707             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13708                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13709             }
13710             if (nBuffers > 1) {
13711                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13712                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13713                 nBytes = MSG_SIZ - 1;
13714             } else {
13715                 (void) memcpy(msg, buffer, nBytes);
13716             }
13717             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13718
13719             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13720                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13721
13722                 archived = TRUE;
13723                 for (i = 0; i < nCmailGames; i ++) {
13724                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13725                         archived = FALSE;
13726                     }
13727                 }
13728                 if (   archived
13729                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13730                         != NULL)) {
13731                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13732                            arcDir,
13733                            appData.cmailGameName,
13734                            gameInfo.date);
13735                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13736                     cmailMsgLoaded = FALSE;
13737                 }
13738             }
13739
13740             DisplayInformation(msg);
13741             pclose(commandOutput);
13742         }
13743     } else {
13744         if ((*cmailMsg) != '\0') {
13745             DisplayInformation(cmailMsg);
13746         }
13747     }
13748
13749     return;
13750 #endif /* !WIN32 */
13751 }
13752
13753 char *
13754 CmailMsg ()
13755 {
13756 #if WIN32
13757     return NULL;
13758 #else
13759     int  prependComma = 0;
13760     char number[5];
13761     char string[MSG_SIZ];       /* Space for game-list */
13762     int  i;
13763
13764     if (!cmailMsgLoaded) return "";
13765
13766     if (cmailMailedMove) {
13767       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13768     } else {
13769         /* Create a list of games left */
13770       snprintf(string, MSG_SIZ, "[");
13771         for (i = 0; i < nCmailGames; i ++) {
13772             if (! (   cmailMoveRegistered[i]
13773                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13774                 if (prependComma) {
13775                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13776                 } else {
13777                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13778                     prependComma = 1;
13779                 }
13780
13781                 strcat(string, number);
13782             }
13783         }
13784         strcat(string, "]");
13785
13786         if (nCmailMovesRegistered + nCmailResults == 0) {
13787             switch (nCmailGames) {
13788               case 1:
13789                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13790                 break;
13791
13792               case 2:
13793                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13794                 break;
13795
13796               default:
13797                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13798                          nCmailGames);
13799                 break;
13800             }
13801         } else {
13802             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13803               case 1:
13804                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13805                          string);
13806                 break;
13807
13808               case 0:
13809                 if (nCmailResults == nCmailGames) {
13810                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13811                 } else {
13812                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13813                 }
13814                 break;
13815
13816               default:
13817                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13818                          string);
13819             }
13820         }
13821     }
13822     return cmailMsg;
13823 #endif /* WIN32 */
13824 }
13825
13826 void
13827 ResetGameEvent ()
13828 {
13829     if (gameMode == Training)
13830       SetTrainingModeOff();
13831
13832     Reset(TRUE, TRUE);
13833     cmailMsgLoaded = FALSE;
13834     if (appData.icsActive) {
13835       SendToICS(ics_prefix);
13836       SendToICS("refresh\n");
13837     }
13838 }
13839
13840 void
13841 ExitEvent (int status)
13842 {
13843     exiting++;
13844     if (exiting > 2) {
13845       /* Give up on clean exit */
13846       exit(status);
13847     }
13848     if (exiting > 1) {
13849       /* Keep trying for clean exit */
13850       return;
13851     }
13852
13853     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13854
13855     if (telnetISR != NULL) {
13856       RemoveInputSource(telnetISR);
13857     }
13858     if (icsPR != NoProc) {
13859       DestroyChildProcess(icsPR, TRUE);
13860     }
13861
13862     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13863     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13864
13865     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13866     /* make sure this other one finishes before killing it!                  */
13867     if(endingGame) { int count = 0;
13868         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13869         while(endingGame && count++ < 10) DoSleep(1);
13870         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13871     }
13872
13873     /* Kill off chess programs */
13874     if (first.pr != NoProc) {
13875         ExitAnalyzeMode();
13876
13877         DoSleep( appData.delayBeforeQuit );
13878         SendToProgram("quit\n", &first);
13879         DoSleep( appData.delayAfterQuit );
13880         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13881     }
13882     if (second.pr != NoProc) {
13883         DoSleep( appData.delayBeforeQuit );
13884         SendToProgram("quit\n", &second);
13885         DoSleep( appData.delayAfterQuit );
13886         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13887     }
13888     if (first.isr != NULL) {
13889         RemoveInputSource(first.isr);
13890     }
13891     if (second.isr != NULL) {
13892         RemoveInputSource(second.isr);
13893     }
13894
13895     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13896     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13897
13898     ShutDownFrontEnd();
13899     exit(status);
13900 }
13901
13902 void
13903 PauseEngine (ChessProgramState *cps)
13904 {
13905     SendToProgram("pause\n", cps);
13906     cps->pause = 2;
13907 }
13908
13909 void
13910 UnPauseEngine (ChessProgramState *cps)
13911 {
13912     SendToProgram("resume\n", cps);
13913     cps->pause = 1;
13914 }
13915
13916 void
13917 PauseEvent ()
13918 {
13919     if (appData.debugMode)
13920         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13921     if (pausing) {
13922         pausing = FALSE;
13923         ModeHighlight();
13924         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13925             StartClocks();
13926             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13927                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13928                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13929             }
13930             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13931             HandleMachineMove(stashedInputMove, stalledEngine);
13932             stalledEngine = NULL;
13933             return;
13934         }
13935         if (gameMode == MachinePlaysWhite ||
13936             gameMode == TwoMachinesPlay   ||
13937             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13938             if(first.pause)  UnPauseEngine(&first);
13939             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13940             if(second.pause) UnPauseEngine(&second);
13941             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13942             StartClocks();
13943         } else {
13944             DisplayBothClocks();
13945         }
13946         if (gameMode == PlayFromGameFile) {
13947             if (appData.timeDelay >= 0)
13948                 AutoPlayGameLoop();
13949         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13950             Reset(FALSE, TRUE);
13951             SendToICS(ics_prefix);
13952             SendToICS("refresh\n");
13953         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13954             ForwardInner(forwardMostMove);
13955         }
13956         pauseExamInvalid = FALSE;
13957     } else {
13958         switch (gameMode) {
13959           default:
13960             return;
13961           case IcsExamining:
13962             pauseExamForwardMostMove = forwardMostMove;
13963             pauseExamInvalid = FALSE;
13964             /* fall through */
13965           case IcsObserving:
13966           case IcsPlayingWhite:
13967           case IcsPlayingBlack:
13968             pausing = TRUE;
13969             ModeHighlight();
13970             return;
13971           case PlayFromGameFile:
13972             (void) StopLoadGameTimer();
13973             pausing = TRUE;
13974             ModeHighlight();
13975             break;
13976           case BeginningOfGame:
13977             if (appData.icsActive) return;
13978             /* else fall through */
13979           case MachinePlaysWhite:
13980           case MachinePlaysBlack:
13981           case TwoMachinesPlay:
13982             if (forwardMostMove == 0)
13983               return;           /* don't pause if no one has moved */
13984             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13985                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13986                 if(onMove->pause) {           // thinking engine can be paused
13987                     PauseEngine(onMove);      // do it
13988                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13989                         PauseEngine(onMove->other);
13990                     else
13991                         SendToProgram("easy\n", onMove->other);
13992                     StopClocks();
13993                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13994             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13995                 if(first.pause) {
13996                     PauseEngine(&first);
13997                     StopClocks();
13998                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13999             } else { // human on move, pause pondering by either method
14000                 if(first.pause)
14001                     PauseEngine(&first);
14002                 else if(appData.ponderNextMove)
14003                     SendToProgram("easy\n", &first);
14004                 StopClocks();
14005             }
14006             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14007           case AnalyzeMode:
14008             pausing = TRUE;
14009             ModeHighlight();
14010             break;
14011         }
14012     }
14013 }
14014
14015 void
14016 EditCommentEvent ()
14017 {
14018     char title[MSG_SIZ];
14019
14020     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14021       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14022     } else {
14023       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14024                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14025                parseList[currentMove - 1]);
14026     }
14027
14028     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14029 }
14030
14031
14032 void
14033 EditTagsEvent ()
14034 {
14035     char *tags = PGNTags(&gameInfo);
14036     bookUp = FALSE;
14037     EditTagsPopUp(tags, NULL);
14038     free(tags);
14039 }
14040
14041 void
14042 ToggleSecond ()
14043 {
14044   if(second.analyzing) {
14045     SendToProgram("exit\n", &second);
14046     second.analyzing = FALSE;
14047   } else {
14048     if (second.pr == NoProc) StartChessProgram(&second);
14049     InitChessProgram(&second, FALSE);
14050     FeedMovesToProgram(&second, currentMove);
14051
14052     SendToProgram("analyze\n", &second);
14053     second.analyzing = TRUE;
14054   }
14055 }
14056
14057 /* Toggle ShowThinking */
14058 void
14059 ToggleShowThinking()
14060 {
14061   appData.showThinking = !appData.showThinking;
14062   ShowThinkingEvent();
14063 }
14064
14065 int
14066 AnalyzeModeEvent ()
14067 {
14068     char buf[MSG_SIZ];
14069
14070     if (!first.analysisSupport) {
14071       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14072       DisplayError(buf, 0);
14073       return 0;
14074     }
14075     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14076     if (appData.icsActive) {
14077         if (gameMode != IcsObserving) {
14078           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14079             DisplayError(buf, 0);
14080             /* secure check */
14081             if (appData.icsEngineAnalyze) {
14082                 if (appData.debugMode)
14083                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14084                 ExitAnalyzeMode();
14085                 ModeHighlight();
14086             }
14087             return 0;
14088         }
14089         /* if enable, user wants to disable icsEngineAnalyze */
14090         if (appData.icsEngineAnalyze) {
14091                 ExitAnalyzeMode();
14092                 ModeHighlight();
14093                 return 0;
14094         }
14095         appData.icsEngineAnalyze = TRUE;
14096         if (appData.debugMode)
14097             fprintf(debugFP, "ICS engine analyze starting... \n");
14098     }
14099
14100     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14101     if (appData.noChessProgram || gameMode == AnalyzeMode)
14102       return 0;
14103
14104     if (gameMode != AnalyzeFile) {
14105         if (!appData.icsEngineAnalyze) {
14106                EditGameEvent();
14107                if (gameMode != EditGame) return 0;
14108         }
14109         if (!appData.showThinking) ToggleShowThinking();
14110         ResurrectChessProgram();
14111         SendToProgram("analyze\n", &first);
14112         first.analyzing = TRUE;
14113         /*first.maybeThinking = TRUE;*/
14114         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14115         EngineOutputPopUp();
14116     }
14117     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14118     pausing = FALSE;
14119     ModeHighlight();
14120     SetGameInfo();
14121
14122     StartAnalysisClock();
14123     GetTimeMark(&lastNodeCountTime);
14124     lastNodeCount = 0;
14125     return 1;
14126 }
14127
14128 void
14129 AnalyzeFileEvent ()
14130 {
14131     if (appData.noChessProgram || gameMode == AnalyzeFile)
14132       return;
14133
14134     if (!first.analysisSupport) {
14135       char buf[MSG_SIZ];
14136       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14137       DisplayError(buf, 0);
14138       return;
14139     }
14140
14141     if (gameMode != AnalyzeMode) {
14142         keepInfo = 1; // mere annotating should not alter PGN tags
14143         EditGameEvent();
14144         keepInfo = 0;
14145         if (gameMode != EditGame) return;
14146         if (!appData.showThinking) ToggleShowThinking();
14147         ResurrectChessProgram();
14148         SendToProgram("analyze\n", &first);
14149         first.analyzing = TRUE;
14150         /*first.maybeThinking = TRUE;*/
14151         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14152         EngineOutputPopUp();
14153     }
14154     gameMode = AnalyzeFile;
14155     pausing = FALSE;
14156     ModeHighlight();
14157
14158     StartAnalysisClock();
14159     GetTimeMark(&lastNodeCountTime);
14160     lastNodeCount = 0;
14161     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14162     AnalysisPeriodicEvent(1);
14163 }
14164
14165 void
14166 MachineWhiteEvent ()
14167 {
14168     char buf[MSG_SIZ];
14169     char *bookHit = NULL;
14170
14171     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14172       return;
14173
14174
14175     if (gameMode == PlayFromGameFile ||
14176         gameMode == TwoMachinesPlay  ||
14177         gameMode == Training         ||
14178         gameMode == AnalyzeMode      ||
14179         gameMode == EndOfGame)
14180         EditGameEvent();
14181
14182     if (gameMode == EditPosition)
14183         EditPositionDone(TRUE);
14184
14185     if (!WhiteOnMove(currentMove)) {
14186         DisplayError(_("It is not White's turn"), 0);
14187         return;
14188     }
14189
14190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14191       ExitAnalyzeMode();
14192
14193     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14194         gameMode == AnalyzeFile)
14195         TruncateGame();
14196
14197     ResurrectChessProgram();    /* in case it isn't running */
14198     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14199         gameMode = MachinePlaysWhite;
14200         ResetClocks();
14201     } else
14202     gameMode = MachinePlaysWhite;
14203     pausing = FALSE;
14204     ModeHighlight();
14205     SetGameInfo();
14206     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14207     DisplayTitle(buf);
14208     if (first.sendName) {
14209       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14210       SendToProgram(buf, &first);
14211     }
14212     if (first.sendTime) {
14213       if (first.useColors) {
14214         SendToProgram("black\n", &first); /*gnu kludge*/
14215       }
14216       SendTimeRemaining(&first, TRUE);
14217     }
14218     if (first.useColors) {
14219       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14220     }
14221     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14222     SetMachineThinkingEnables();
14223     first.maybeThinking = TRUE;
14224     StartClocks();
14225     firstMove = FALSE;
14226
14227     if (appData.autoFlipView && !flipView) {
14228       flipView = !flipView;
14229       DrawPosition(FALSE, NULL);
14230       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14231     }
14232
14233     if(bookHit) { // [HGM] book: simulate book reply
14234         static char bookMove[MSG_SIZ]; // a bit generous?
14235
14236         programStats.nodes = programStats.depth = programStats.time =
14237         programStats.score = programStats.got_only_move = 0;
14238         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14239
14240         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14241         strcat(bookMove, bookHit);
14242         HandleMachineMove(bookMove, &first);
14243     }
14244 }
14245
14246 void
14247 MachineBlackEvent ()
14248 {
14249   char buf[MSG_SIZ];
14250   char *bookHit = NULL;
14251
14252     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14253         return;
14254
14255
14256     if (gameMode == PlayFromGameFile ||
14257         gameMode == TwoMachinesPlay  ||
14258         gameMode == Training         ||
14259         gameMode == AnalyzeMode      ||
14260         gameMode == EndOfGame)
14261         EditGameEvent();
14262
14263     if (gameMode == EditPosition)
14264         EditPositionDone(TRUE);
14265
14266     if (WhiteOnMove(currentMove)) {
14267         DisplayError(_("It is not Black's turn"), 0);
14268         return;
14269     }
14270
14271     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14272       ExitAnalyzeMode();
14273
14274     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14275         gameMode == AnalyzeFile)
14276         TruncateGame();
14277
14278     ResurrectChessProgram();    /* in case it isn't running */
14279     gameMode = MachinePlaysBlack;
14280     pausing = FALSE;
14281     ModeHighlight();
14282     SetGameInfo();
14283     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14284     DisplayTitle(buf);
14285     if (first.sendName) {
14286       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14287       SendToProgram(buf, &first);
14288     }
14289     if (first.sendTime) {
14290       if (first.useColors) {
14291         SendToProgram("white\n", &first); /*gnu kludge*/
14292       }
14293       SendTimeRemaining(&first, FALSE);
14294     }
14295     if (first.useColors) {
14296       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14297     }
14298     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14299     SetMachineThinkingEnables();
14300     first.maybeThinking = TRUE;
14301     StartClocks();
14302
14303     if (appData.autoFlipView && flipView) {
14304       flipView = !flipView;
14305       DrawPosition(FALSE, NULL);
14306       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14307     }
14308     if(bookHit) { // [HGM] book: simulate book reply
14309         static char bookMove[MSG_SIZ]; // a bit generous?
14310
14311         programStats.nodes = programStats.depth = programStats.time =
14312         programStats.score = programStats.got_only_move = 0;
14313         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14314
14315         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14316         strcat(bookMove, bookHit);
14317         HandleMachineMove(bookMove, &first);
14318     }
14319 }
14320
14321
14322 void
14323 DisplayTwoMachinesTitle ()
14324 {
14325     char buf[MSG_SIZ];
14326     if (appData.matchGames > 0) {
14327         if(appData.tourneyFile[0]) {
14328           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14329                    gameInfo.white, _("vs."), gameInfo.black,
14330                    nextGame+1, appData.matchGames+1,
14331                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14332         } else
14333         if (first.twoMachinesColor[0] == 'w') {
14334           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14335                    gameInfo.white, _("vs."),  gameInfo.black,
14336                    first.matchWins, second.matchWins,
14337                    matchGame - 1 - (first.matchWins + second.matchWins));
14338         } else {
14339           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14340                    gameInfo.white, _("vs."), gameInfo.black,
14341                    second.matchWins, first.matchWins,
14342                    matchGame - 1 - (first.matchWins + second.matchWins));
14343         }
14344     } else {
14345       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14346     }
14347     DisplayTitle(buf);
14348 }
14349
14350 void
14351 SettingsMenuIfReady ()
14352 {
14353   if (second.lastPing != second.lastPong) {
14354     DisplayMessage("", _("Waiting for second chess program"));
14355     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14356     return;
14357   }
14358   ThawUI();
14359   DisplayMessage("", "");
14360   SettingsPopUp(&second);
14361 }
14362
14363 int
14364 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14365 {
14366     char buf[MSG_SIZ];
14367     if (cps->pr == NoProc) {
14368         StartChessProgram(cps);
14369         if (cps->protocolVersion == 1) {
14370           retry();
14371           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14372         } else {
14373           /* kludge: allow timeout for initial "feature" command */
14374           if(retry != TwoMachinesEventIfReady) FreezeUI();
14375           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14376           DisplayMessage("", buf);
14377           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14378         }
14379         return 1;
14380     }
14381     return 0;
14382 }
14383
14384 void
14385 TwoMachinesEvent P((void))
14386 {
14387     int i;
14388     char buf[MSG_SIZ];
14389     ChessProgramState *onmove;
14390     char *bookHit = NULL;
14391     static int stalling = 0;
14392     TimeMark now;
14393     long wait;
14394
14395     if (appData.noChessProgram) return;
14396
14397     switch (gameMode) {
14398       case TwoMachinesPlay:
14399         return;
14400       case MachinePlaysWhite:
14401       case MachinePlaysBlack:
14402         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14403             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14404             return;
14405         }
14406         /* fall through */
14407       case BeginningOfGame:
14408       case PlayFromGameFile:
14409       case EndOfGame:
14410         EditGameEvent();
14411         if (gameMode != EditGame) return;
14412         break;
14413       case EditPosition:
14414         EditPositionDone(TRUE);
14415         break;
14416       case AnalyzeMode:
14417       case AnalyzeFile:
14418         ExitAnalyzeMode();
14419         break;
14420       case EditGame:
14421       default:
14422         break;
14423     }
14424
14425 //    forwardMostMove = currentMove;
14426     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14427     startingEngine = TRUE;
14428
14429     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14430
14431     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14432     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14433       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14434       return;
14435     }
14436     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14437
14438     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14439                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14440         startingEngine = FALSE;
14441         DisplayError("second engine does not play this", 0);
14442         return;
14443     }
14444
14445     if(!stalling) {
14446       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14447       SendToProgram("force\n", &second);
14448       stalling = 1;
14449       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14450       return;
14451     }
14452     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14453     if(appData.matchPause>10000 || appData.matchPause<10)
14454                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14455     wait = SubtractTimeMarks(&now, &pauseStart);
14456     if(wait < appData.matchPause) {
14457         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14458         return;
14459     }
14460     // we are now committed to starting the game
14461     stalling = 0;
14462     DisplayMessage("", "");
14463     if (startedFromSetupPosition) {
14464         SendBoard(&second, backwardMostMove);
14465     if (appData.debugMode) {
14466         fprintf(debugFP, "Two Machines\n");
14467     }
14468     }
14469     for (i = backwardMostMove; i < forwardMostMove; i++) {
14470         SendMoveToProgram(i, &second);
14471     }
14472
14473     gameMode = TwoMachinesPlay;
14474     pausing = startingEngine = FALSE;
14475     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14476     SetGameInfo();
14477     DisplayTwoMachinesTitle();
14478     firstMove = TRUE;
14479     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14480         onmove = &first;
14481     } else {
14482         onmove = &second;
14483     }
14484     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14485     SendToProgram(first.computerString, &first);
14486     if (first.sendName) {
14487       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14488       SendToProgram(buf, &first);
14489     }
14490     SendToProgram(second.computerString, &second);
14491     if (second.sendName) {
14492       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14493       SendToProgram(buf, &second);
14494     }
14495
14496     ResetClocks();
14497     if (!first.sendTime || !second.sendTime) {
14498         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14499         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14500     }
14501     if (onmove->sendTime) {
14502       if (onmove->useColors) {
14503         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14504       }
14505       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14506     }
14507     if (onmove->useColors) {
14508       SendToProgram(onmove->twoMachinesColor, onmove);
14509     }
14510     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14511 //    SendToProgram("go\n", onmove);
14512     onmove->maybeThinking = TRUE;
14513     SetMachineThinkingEnables();
14514
14515     StartClocks();
14516
14517     if(bookHit) { // [HGM] book: simulate book reply
14518         static char bookMove[MSG_SIZ]; // a bit generous?
14519
14520         programStats.nodes = programStats.depth = programStats.time =
14521         programStats.score = programStats.got_only_move = 0;
14522         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14523
14524         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14525         strcat(bookMove, bookHit);
14526         savedMessage = bookMove; // args for deferred call
14527         savedState = onmove;
14528         ScheduleDelayedEvent(DeferredBookMove, 1);
14529     }
14530 }
14531
14532 void
14533 TrainingEvent ()
14534 {
14535     if (gameMode == Training) {
14536       SetTrainingModeOff();
14537       gameMode = PlayFromGameFile;
14538       DisplayMessage("", _("Training mode off"));
14539     } else {
14540       gameMode = Training;
14541       animateTraining = appData.animate;
14542
14543       /* make sure we are not already at the end of the game */
14544       if (currentMove < forwardMostMove) {
14545         SetTrainingModeOn();
14546         DisplayMessage("", _("Training mode on"));
14547       } else {
14548         gameMode = PlayFromGameFile;
14549         DisplayError(_("Already at end of game"), 0);
14550       }
14551     }
14552     ModeHighlight();
14553 }
14554
14555 void
14556 IcsClientEvent ()
14557 {
14558     if (!appData.icsActive) return;
14559     switch (gameMode) {
14560       case IcsPlayingWhite:
14561       case IcsPlayingBlack:
14562       case IcsObserving:
14563       case IcsIdle:
14564       case BeginningOfGame:
14565       case IcsExamining:
14566         return;
14567
14568       case EditGame:
14569         break;
14570
14571       case EditPosition:
14572         EditPositionDone(TRUE);
14573         break;
14574
14575       case AnalyzeMode:
14576       case AnalyzeFile:
14577         ExitAnalyzeMode();
14578         break;
14579
14580       default:
14581         EditGameEvent();
14582         break;
14583     }
14584
14585     gameMode = IcsIdle;
14586     ModeHighlight();
14587     return;
14588 }
14589
14590 void
14591 EditGameEvent ()
14592 {
14593     int i;
14594
14595     switch (gameMode) {
14596       case Training:
14597         SetTrainingModeOff();
14598         break;
14599       case MachinePlaysWhite:
14600       case MachinePlaysBlack:
14601       case BeginningOfGame:
14602         SendToProgram("force\n", &first);
14603         SetUserThinkingEnables();
14604         break;
14605       case PlayFromGameFile:
14606         (void) StopLoadGameTimer();
14607         if (gameFileFP != NULL) {
14608             gameFileFP = NULL;
14609         }
14610         break;
14611       case EditPosition:
14612         EditPositionDone(TRUE);
14613         break;
14614       case AnalyzeMode:
14615       case AnalyzeFile:
14616         ExitAnalyzeMode();
14617         SendToProgram("force\n", &first);
14618         break;
14619       case TwoMachinesPlay:
14620         GameEnds(EndOfFile, NULL, GE_PLAYER);
14621         ResurrectChessProgram();
14622         SetUserThinkingEnables();
14623         break;
14624       case EndOfGame:
14625         ResurrectChessProgram();
14626         break;
14627       case IcsPlayingBlack:
14628       case IcsPlayingWhite:
14629         DisplayError(_("Warning: You are still playing a game"), 0);
14630         break;
14631       case IcsObserving:
14632         DisplayError(_("Warning: You are still observing a game"), 0);
14633         break;
14634       case IcsExamining:
14635         DisplayError(_("Warning: You are still examining a game"), 0);
14636         break;
14637       case IcsIdle:
14638         break;
14639       case EditGame:
14640       default:
14641         return;
14642     }
14643
14644     pausing = FALSE;
14645     StopClocks();
14646     first.offeredDraw = second.offeredDraw = 0;
14647
14648     if (gameMode == PlayFromGameFile) {
14649         whiteTimeRemaining = timeRemaining[0][currentMove];
14650         blackTimeRemaining = timeRemaining[1][currentMove];
14651         DisplayTitle("");
14652     }
14653
14654     if (gameMode == MachinePlaysWhite ||
14655         gameMode == MachinePlaysBlack ||
14656         gameMode == TwoMachinesPlay ||
14657         gameMode == EndOfGame) {
14658         i = forwardMostMove;
14659         while (i > currentMove) {
14660             SendToProgram("undo\n", &first);
14661             i--;
14662         }
14663         if(!adjustedClock) {
14664         whiteTimeRemaining = timeRemaining[0][currentMove];
14665         blackTimeRemaining = timeRemaining[1][currentMove];
14666         DisplayBothClocks();
14667         }
14668         if (whiteFlag || blackFlag) {
14669             whiteFlag = blackFlag = 0;
14670         }
14671         DisplayTitle("");
14672     }
14673
14674     gameMode = EditGame;
14675     ModeHighlight();
14676     SetGameInfo();
14677 }
14678
14679
14680 void
14681 EditPositionEvent ()
14682 {
14683     if (gameMode == EditPosition) {
14684         EditGameEvent();
14685         return;
14686     }
14687
14688     EditGameEvent();
14689     if (gameMode != EditGame) return;
14690
14691     gameMode = EditPosition;
14692     ModeHighlight();
14693     SetGameInfo();
14694     if (currentMove > 0)
14695       CopyBoard(boards[0], boards[currentMove]);
14696
14697     blackPlaysFirst = !WhiteOnMove(currentMove);
14698     ResetClocks();
14699     currentMove = forwardMostMove = backwardMostMove = 0;
14700     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14701     DisplayMove(-1);
14702     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14703 }
14704
14705 void
14706 ExitAnalyzeMode ()
14707 {
14708     /* [DM] icsEngineAnalyze - possible call from other functions */
14709     if (appData.icsEngineAnalyze) {
14710         appData.icsEngineAnalyze = FALSE;
14711
14712         DisplayMessage("",_("Close ICS engine analyze..."));
14713     }
14714     if (first.analysisSupport && first.analyzing) {
14715       SendToBoth("exit\n");
14716       first.analyzing = second.analyzing = FALSE;
14717     }
14718     thinkOutput[0] = NULLCHAR;
14719 }
14720
14721 void
14722 EditPositionDone (Boolean fakeRights)
14723 {
14724     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14725
14726     startedFromSetupPosition = TRUE;
14727     InitChessProgram(&first, FALSE);
14728     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14729       boards[0][EP_STATUS] = EP_NONE;
14730       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14731       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14732         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14733         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14734       } else boards[0][CASTLING][2] = NoRights;
14735       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14736         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14737         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14738       } else boards[0][CASTLING][5] = NoRights;
14739       if(gameInfo.variant == VariantSChess) {
14740         int i;
14741         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14742           boards[0][VIRGIN][i] = 0;
14743           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14744           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14745         }
14746       }
14747     }
14748     SendToProgram("force\n", &first);
14749     if (blackPlaysFirst) {
14750         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14751         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14752         currentMove = forwardMostMove = backwardMostMove = 1;
14753         CopyBoard(boards[1], boards[0]);
14754     } else {
14755         currentMove = forwardMostMove = backwardMostMove = 0;
14756     }
14757     SendBoard(&first, forwardMostMove);
14758     if (appData.debugMode) {
14759         fprintf(debugFP, "EditPosDone\n");
14760     }
14761     DisplayTitle("");
14762     DisplayMessage("", "");
14763     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14764     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14765     gameMode = EditGame;
14766     ModeHighlight();
14767     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14768     ClearHighlights(); /* [AS] */
14769 }
14770
14771 /* Pause for `ms' milliseconds */
14772 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14773 void
14774 TimeDelay (long ms)
14775 {
14776     TimeMark m1, m2;
14777
14778     GetTimeMark(&m1);
14779     do {
14780         GetTimeMark(&m2);
14781     } while (SubtractTimeMarks(&m2, &m1) < ms);
14782 }
14783
14784 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14785 void
14786 SendMultiLineToICS (char *buf)
14787 {
14788     char temp[MSG_SIZ+1], *p;
14789     int len;
14790
14791     len = strlen(buf);
14792     if (len > MSG_SIZ)
14793       len = MSG_SIZ;
14794
14795     strncpy(temp, buf, len);
14796     temp[len] = 0;
14797
14798     p = temp;
14799     while (*p) {
14800         if (*p == '\n' || *p == '\r')
14801           *p = ' ';
14802         ++p;
14803     }
14804
14805     strcat(temp, "\n");
14806     SendToICS(temp);
14807     SendToPlayer(temp, strlen(temp));
14808 }
14809
14810 void
14811 SetWhiteToPlayEvent ()
14812 {
14813     if (gameMode == EditPosition) {
14814         blackPlaysFirst = FALSE;
14815         DisplayBothClocks();    /* works because currentMove is 0 */
14816     } else if (gameMode == IcsExamining) {
14817         SendToICS(ics_prefix);
14818         SendToICS("tomove white\n");
14819     }
14820 }
14821
14822 void
14823 SetBlackToPlayEvent ()
14824 {
14825     if (gameMode == EditPosition) {
14826         blackPlaysFirst = TRUE;
14827         currentMove = 1;        /* kludge */
14828         DisplayBothClocks();
14829         currentMove = 0;
14830     } else if (gameMode == IcsExamining) {
14831         SendToICS(ics_prefix);
14832         SendToICS("tomove black\n");
14833     }
14834 }
14835
14836 void
14837 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14838 {
14839     char buf[MSG_SIZ];
14840     ChessSquare piece = boards[0][y][x];
14841     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14842     static int lastVariant;
14843
14844     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14845
14846     switch (selection) {
14847       case ClearBoard:
14848         CopyBoard(currentBoard, boards[0]);
14849         CopyBoard(menuBoard, initialPosition);
14850         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14851             SendToICS(ics_prefix);
14852             SendToICS("bsetup clear\n");
14853         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14854             SendToICS(ics_prefix);
14855             SendToICS("clearboard\n");
14856         } else {
14857             int nonEmpty = 0;
14858             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14859                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14860                 for (y = 0; y < BOARD_HEIGHT; y++) {
14861                     if (gameMode == IcsExamining) {
14862                         if (boards[currentMove][y][x] != EmptySquare) {
14863                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14864                                     AAA + x, ONE + y);
14865                             SendToICS(buf);
14866                         }
14867                     } else {
14868                         if(boards[0][y][x] != p) nonEmpty++;
14869                         boards[0][y][x] = p;
14870                     }
14871                 }
14872                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14873             }
14874             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14875                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14876                     ChessSquare p = menuBoard[0][x];
14877                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14878                     p = menuBoard[BOARD_HEIGHT-1][x];
14879                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14880                 }
14881                 DisplayMessage("Clicking clock again restores position", "");
14882                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14883                 if(!nonEmpty) { // asked to clear an empty board
14884                     CopyBoard(boards[0], menuBoard);
14885                 } else
14886                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14887                     CopyBoard(boards[0], initialPosition);
14888                 } else
14889                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14890                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14891                     CopyBoard(boards[0], erasedBoard);
14892                 } else
14893                     CopyBoard(erasedBoard, currentBoard);
14894
14895             }
14896         }
14897         if (gameMode == EditPosition) {
14898             DrawPosition(FALSE, boards[0]);
14899         }
14900         break;
14901
14902       case WhitePlay:
14903         SetWhiteToPlayEvent();
14904         break;
14905
14906       case BlackPlay:
14907         SetBlackToPlayEvent();
14908         break;
14909
14910       case EmptySquare:
14911         if (gameMode == IcsExamining) {
14912             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14913             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14914             SendToICS(buf);
14915         } else {
14916             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14917                 if(x == BOARD_LEFT-2) {
14918                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14919                     boards[0][y][1] = 0;
14920                 } else
14921                 if(x == BOARD_RGHT+1) {
14922                     if(y >= gameInfo.holdingsSize) break;
14923                     boards[0][y][BOARD_WIDTH-2] = 0;
14924                 } else break;
14925             }
14926             boards[0][y][x] = EmptySquare;
14927             DrawPosition(FALSE, boards[0]);
14928         }
14929         break;
14930
14931       case PromotePiece:
14932         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14933            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14934             selection = (ChessSquare) (PROMOTED piece);
14935         } else if(piece == EmptySquare) selection = WhiteSilver;
14936         else selection = (ChessSquare)((int)piece - 1);
14937         goto defaultlabel;
14938
14939       case DemotePiece:
14940         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14941            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14942             selection = (ChessSquare) (DEMOTED piece);
14943         } else if(piece == EmptySquare) selection = BlackSilver;
14944         else selection = (ChessSquare)((int)piece + 1);
14945         goto defaultlabel;
14946
14947       case WhiteQueen:
14948       case BlackQueen:
14949         if(gameInfo.variant == VariantShatranj ||
14950            gameInfo.variant == VariantXiangqi  ||
14951            gameInfo.variant == VariantCourier  ||
14952            gameInfo.variant == VariantASEAN    ||
14953            gameInfo.variant == VariantMakruk     )
14954             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14955         goto defaultlabel;
14956
14957       case WhiteKing:
14958       case BlackKing:
14959         if(gameInfo.variant == VariantXiangqi)
14960             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14961         if(gameInfo.variant == VariantKnightmate)
14962             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14963       default:
14964         defaultlabel:
14965         if (gameMode == IcsExamining) {
14966             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14967             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14968                      PieceToChar(selection), AAA + x, ONE + y);
14969             SendToICS(buf);
14970         } else {
14971             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14972                 int n;
14973                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14974                     n = PieceToNumber(selection - BlackPawn);
14975                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14976                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14977                     boards[0][BOARD_HEIGHT-1-n][1]++;
14978                 } else
14979                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14980                     n = PieceToNumber(selection);
14981                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14982                     boards[0][n][BOARD_WIDTH-1] = selection;
14983                     boards[0][n][BOARD_WIDTH-2]++;
14984                 }
14985             } else
14986             boards[0][y][x] = selection;
14987             DrawPosition(TRUE, boards[0]);
14988             ClearHighlights();
14989             fromX = fromY = -1;
14990         }
14991         break;
14992     }
14993 }
14994
14995
14996 void
14997 DropMenuEvent (ChessSquare selection, int x, int y)
14998 {
14999     ChessMove moveType;
15000
15001     switch (gameMode) {
15002       case IcsPlayingWhite:
15003       case MachinePlaysBlack:
15004         if (!WhiteOnMove(currentMove)) {
15005             DisplayMoveError(_("It is Black's turn"));
15006             return;
15007         }
15008         moveType = WhiteDrop;
15009         break;
15010       case IcsPlayingBlack:
15011       case MachinePlaysWhite:
15012         if (WhiteOnMove(currentMove)) {
15013             DisplayMoveError(_("It is White's turn"));
15014             return;
15015         }
15016         moveType = BlackDrop;
15017         break;
15018       case EditGame:
15019         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15020         break;
15021       default:
15022         return;
15023     }
15024
15025     if (moveType == BlackDrop && selection < BlackPawn) {
15026       selection = (ChessSquare) ((int) selection
15027                                  + (int) BlackPawn - (int) WhitePawn);
15028     }
15029     if (boards[currentMove][y][x] != EmptySquare) {
15030         DisplayMoveError(_("That square is occupied"));
15031         return;
15032     }
15033
15034     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15035 }
15036
15037 void
15038 AcceptEvent ()
15039 {
15040     /* Accept a pending offer of any kind from opponent */
15041
15042     if (appData.icsActive) {
15043         SendToICS(ics_prefix);
15044         SendToICS("accept\n");
15045     } else if (cmailMsgLoaded) {
15046         if (currentMove == cmailOldMove &&
15047             commentList[cmailOldMove] != NULL &&
15048             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15049                    "Black offers a draw" : "White offers a draw")) {
15050             TruncateGame();
15051             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15052             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15053         } else {
15054             DisplayError(_("There is no pending offer on this move"), 0);
15055             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15056         }
15057     } else {
15058         /* Not used for offers from chess program */
15059     }
15060 }
15061
15062 void
15063 DeclineEvent ()
15064 {
15065     /* Decline a pending offer of any kind from opponent */
15066
15067     if (appData.icsActive) {
15068         SendToICS(ics_prefix);
15069         SendToICS("decline\n");
15070     } else if (cmailMsgLoaded) {
15071         if (currentMove == cmailOldMove &&
15072             commentList[cmailOldMove] != NULL &&
15073             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15074                    "Black offers a draw" : "White offers a draw")) {
15075 #ifdef NOTDEF
15076             AppendComment(cmailOldMove, "Draw declined", TRUE);
15077             DisplayComment(cmailOldMove - 1, "Draw declined");
15078 #endif /*NOTDEF*/
15079         } else {
15080             DisplayError(_("There is no pending offer on this move"), 0);
15081         }
15082     } else {
15083         /* Not used for offers from chess program */
15084     }
15085 }
15086
15087 void
15088 RematchEvent ()
15089 {
15090     /* Issue ICS rematch command */
15091     if (appData.icsActive) {
15092         SendToICS(ics_prefix);
15093         SendToICS("rematch\n");
15094     }
15095 }
15096
15097 void
15098 CallFlagEvent ()
15099 {
15100     /* Call your opponent's flag (claim a win on time) */
15101     if (appData.icsActive) {
15102         SendToICS(ics_prefix);
15103         SendToICS("flag\n");
15104     } else {
15105         switch (gameMode) {
15106           default:
15107             return;
15108           case MachinePlaysWhite:
15109             if (whiteFlag) {
15110                 if (blackFlag)
15111                   GameEnds(GameIsDrawn, "Both players ran out of time",
15112                            GE_PLAYER);
15113                 else
15114                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15115             } else {
15116                 DisplayError(_("Your opponent is not out of time"), 0);
15117             }
15118             break;
15119           case MachinePlaysBlack:
15120             if (blackFlag) {
15121                 if (whiteFlag)
15122                   GameEnds(GameIsDrawn, "Both players ran out of time",
15123                            GE_PLAYER);
15124                 else
15125                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15126             } else {
15127                 DisplayError(_("Your opponent is not out of time"), 0);
15128             }
15129             break;
15130         }
15131     }
15132 }
15133
15134 void
15135 ClockClick (int which)
15136 {       // [HGM] code moved to back-end from winboard.c
15137         if(which) { // black clock
15138           if (gameMode == EditPosition || gameMode == IcsExamining) {
15139             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15140             SetBlackToPlayEvent();
15141           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15142           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15143           } else if (shiftKey) {
15144             AdjustClock(which, -1);
15145           } else if (gameMode == IcsPlayingWhite ||
15146                      gameMode == MachinePlaysBlack) {
15147             CallFlagEvent();
15148           }
15149         } else { // white clock
15150           if (gameMode == EditPosition || gameMode == IcsExamining) {
15151             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15152             SetWhiteToPlayEvent();
15153           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15154           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15155           } else if (shiftKey) {
15156             AdjustClock(which, -1);
15157           } else if (gameMode == IcsPlayingBlack ||
15158                    gameMode == MachinePlaysWhite) {
15159             CallFlagEvent();
15160           }
15161         }
15162 }
15163
15164 void
15165 DrawEvent ()
15166 {
15167     /* Offer draw or accept pending draw offer from opponent */
15168
15169     if (appData.icsActive) {
15170         /* Note: tournament rules require draw offers to be
15171            made after you make your move but before you punch
15172            your clock.  Currently ICS doesn't let you do that;
15173            instead, you immediately punch your clock after making
15174            a move, but you can offer a draw at any time. */
15175
15176         SendToICS(ics_prefix);
15177         SendToICS("draw\n");
15178         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15179     } else if (cmailMsgLoaded) {
15180         if (currentMove == cmailOldMove &&
15181             commentList[cmailOldMove] != NULL &&
15182             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15183                    "Black offers a draw" : "White offers a draw")) {
15184             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15185             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15186         } else if (currentMove == cmailOldMove + 1) {
15187             char *offer = WhiteOnMove(cmailOldMove) ?
15188               "White offers a draw" : "Black offers a draw";
15189             AppendComment(currentMove, offer, TRUE);
15190             DisplayComment(currentMove - 1, offer);
15191             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15192         } else {
15193             DisplayError(_("You must make your move before offering a draw"), 0);
15194             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15195         }
15196     } else if (first.offeredDraw) {
15197         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15198     } else {
15199         if (first.sendDrawOffers) {
15200             SendToProgram("draw\n", &first);
15201             userOfferedDraw = TRUE;
15202         }
15203     }
15204 }
15205
15206 void
15207 AdjournEvent ()
15208 {
15209     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15210
15211     if (appData.icsActive) {
15212         SendToICS(ics_prefix);
15213         SendToICS("adjourn\n");
15214     } else {
15215         /* Currently GNU Chess doesn't offer or accept Adjourns */
15216     }
15217 }
15218
15219
15220 void
15221 AbortEvent ()
15222 {
15223     /* Offer Abort or accept pending Abort offer from opponent */
15224
15225     if (appData.icsActive) {
15226         SendToICS(ics_prefix);
15227         SendToICS("abort\n");
15228     } else {
15229         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15230     }
15231 }
15232
15233 void
15234 ResignEvent ()
15235 {
15236     /* Resign.  You can do this even if it's not your turn. */
15237
15238     if (appData.icsActive) {
15239         SendToICS(ics_prefix);
15240         SendToICS("resign\n");
15241     } else {
15242         switch (gameMode) {
15243           case MachinePlaysWhite:
15244             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15245             break;
15246           case MachinePlaysBlack:
15247             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15248             break;
15249           case EditGame:
15250             if (cmailMsgLoaded) {
15251                 TruncateGame();
15252                 if (WhiteOnMove(cmailOldMove)) {
15253                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15254                 } else {
15255                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15256                 }
15257                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15258             }
15259             break;
15260           default:
15261             break;
15262         }
15263     }
15264 }
15265
15266
15267 void
15268 StopObservingEvent ()
15269 {
15270     /* Stop observing current games */
15271     SendToICS(ics_prefix);
15272     SendToICS("unobserve\n");
15273 }
15274
15275 void
15276 StopExaminingEvent ()
15277 {
15278     /* Stop observing current game */
15279     SendToICS(ics_prefix);
15280     SendToICS("unexamine\n");
15281 }
15282
15283 void
15284 ForwardInner (int target)
15285 {
15286     int limit; int oldSeekGraphUp = seekGraphUp;
15287
15288     if (appData.debugMode)
15289         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15290                 target, currentMove, forwardMostMove);
15291
15292     if (gameMode == EditPosition)
15293       return;
15294
15295     seekGraphUp = FALSE;
15296     MarkTargetSquares(1);
15297
15298     if (gameMode == PlayFromGameFile && !pausing)
15299       PauseEvent();
15300
15301     if (gameMode == IcsExamining && pausing)
15302       limit = pauseExamForwardMostMove;
15303     else
15304       limit = forwardMostMove;
15305
15306     if (target > limit) target = limit;
15307
15308     if (target > 0 && moveList[target - 1][0]) {
15309         int fromX, fromY, toX, toY;
15310         toX = moveList[target - 1][2] - AAA;
15311         toY = moveList[target - 1][3] - ONE;
15312         if (moveList[target - 1][1] == '@') {
15313             if (appData.highlightLastMove) {
15314                 SetHighlights(-1, -1, toX, toY);
15315             }
15316         } else {
15317             fromX = moveList[target - 1][0] - AAA;
15318             fromY = moveList[target - 1][1] - ONE;
15319             if (target == currentMove + 1) {
15320                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15321             }
15322             if (appData.highlightLastMove) {
15323                 SetHighlights(fromX, fromY, toX, toY);
15324             }
15325         }
15326     }
15327     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15328         gameMode == Training || gameMode == PlayFromGameFile ||
15329         gameMode == AnalyzeFile) {
15330         while (currentMove < target) {
15331             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15332             SendMoveToProgram(currentMove++, &first);
15333         }
15334     } else {
15335         currentMove = target;
15336     }
15337
15338     if (gameMode == EditGame || gameMode == EndOfGame) {
15339         whiteTimeRemaining = timeRemaining[0][currentMove];
15340         blackTimeRemaining = timeRemaining[1][currentMove];
15341     }
15342     DisplayBothClocks();
15343     DisplayMove(currentMove - 1);
15344     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15345     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15346     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15347         DisplayComment(currentMove - 1, commentList[currentMove]);
15348     }
15349     ClearMap(); // [HGM] exclude: invalidate map
15350 }
15351
15352
15353 void
15354 ForwardEvent ()
15355 {
15356     if (gameMode == IcsExamining && !pausing) {
15357         SendToICS(ics_prefix);
15358         SendToICS("forward\n");
15359     } else {
15360         ForwardInner(currentMove + 1);
15361     }
15362 }
15363
15364 void
15365 ToEndEvent ()
15366 {
15367     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15368         /* to optimze, we temporarily turn off analysis mode while we feed
15369          * the remaining moves to the engine. Otherwise we get analysis output
15370          * after each move.
15371          */
15372         if (first.analysisSupport) {
15373           SendToProgram("exit\nforce\n", &first);
15374           first.analyzing = FALSE;
15375         }
15376     }
15377
15378     if (gameMode == IcsExamining && !pausing) {
15379         SendToICS(ics_prefix);
15380         SendToICS("forward 999999\n");
15381     } else {
15382         ForwardInner(forwardMostMove);
15383     }
15384
15385     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15386         /* we have fed all the moves, so reactivate analysis mode */
15387         SendToProgram("analyze\n", &first);
15388         first.analyzing = TRUE;
15389         /*first.maybeThinking = TRUE;*/
15390         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15391     }
15392 }
15393
15394 void
15395 BackwardInner (int target)
15396 {
15397     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15398
15399     if (appData.debugMode)
15400         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15401                 target, currentMove, forwardMostMove);
15402
15403     if (gameMode == EditPosition) return;
15404     seekGraphUp = FALSE;
15405     MarkTargetSquares(1);
15406     if (currentMove <= backwardMostMove) {
15407         ClearHighlights();
15408         DrawPosition(full_redraw, boards[currentMove]);
15409         return;
15410     }
15411     if (gameMode == PlayFromGameFile && !pausing)
15412       PauseEvent();
15413
15414     if (moveList[target][0]) {
15415         int fromX, fromY, toX, toY;
15416         toX = moveList[target][2] - AAA;
15417         toY = moveList[target][3] - ONE;
15418         if (moveList[target][1] == '@') {
15419             if (appData.highlightLastMove) {
15420                 SetHighlights(-1, -1, toX, toY);
15421             }
15422         } else {
15423             fromX = moveList[target][0] - AAA;
15424             fromY = moveList[target][1] - ONE;
15425             if (target == currentMove - 1) {
15426                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15427             }
15428             if (appData.highlightLastMove) {
15429                 SetHighlights(fromX, fromY, toX, toY);
15430             }
15431         }
15432     }
15433     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15434         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15435         while (currentMove > target) {
15436             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15437                 // null move cannot be undone. Reload program with move history before it.
15438                 int i;
15439                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15440                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15441                 }
15442                 SendBoard(&first, i);
15443               if(second.analyzing) SendBoard(&second, i);
15444                 for(currentMove=i; currentMove<target; currentMove++) {
15445                     SendMoveToProgram(currentMove, &first);
15446                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15447                 }
15448                 break;
15449             }
15450             SendToBoth("undo\n");
15451             currentMove--;
15452         }
15453     } else {
15454         currentMove = target;
15455     }
15456
15457     if (gameMode == EditGame || gameMode == EndOfGame) {
15458         whiteTimeRemaining = timeRemaining[0][currentMove];
15459         blackTimeRemaining = timeRemaining[1][currentMove];
15460     }
15461     DisplayBothClocks();
15462     DisplayMove(currentMove - 1);
15463     DrawPosition(full_redraw, boards[currentMove]);
15464     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15465     // [HGM] PV info: routine tests if comment empty
15466     DisplayComment(currentMove - 1, commentList[currentMove]);
15467     ClearMap(); // [HGM] exclude: invalidate map
15468 }
15469
15470 void
15471 BackwardEvent ()
15472 {
15473     if (gameMode == IcsExamining && !pausing) {
15474         SendToICS(ics_prefix);
15475         SendToICS("backward\n");
15476     } else {
15477         BackwardInner(currentMove - 1);
15478     }
15479 }
15480
15481 void
15482 ToStartEvent ()
15483 {
15484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15485         /* to optimize, we temporarily turn off analysis mode while we undo
15486          * all the moves. Otherwise we get analysis output after each undo.
15487          */
15488         if (first.analysisSupport) {
15489           SendToProgram("exit\nforce\n", &first);
15490           first.analyzing = FALSE;
15491         }
15492     }
15493
15494     if (gameMode == IcsExamining && !pausing) {
15495         SendToICS(ics_prefix);
15496         SendToICS("backward 999999\n");
15497     } else {
15498         BackwardInner(backwardMostMove);
15499     }
15500
15501     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15502         /* we have fed all the moves, so reactivate analysis mode */
15503         SendToProgram("analyze\n", &first);
15504         first.analyzing = TRUE;
15505         /*first.maybeThinking = TRUE;*/
15506         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15507     }
15508 }
15509
15510 void
15511 ToNrEvent (int to)
15512 {
15513   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15514   if (to >= forwardMostMove) to = forwardMostMove;
15515   if (to <= backwardMostMove) to = backwardMostMove;
15516   if (to < currentMove) {
15517     BackwardInner(to);
15518   } else {
15519     ForwardInner(to);
15520   }
15521 }
15522
15523 void
15524 RevertEvent (Boolean annotate)
15525 {
15526     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15527         return;
15528     }
15529     if (gameMode != IcsExamining) {
15530         DisplayError(_("You are not examining a game"), 0);
15531         return;
15532     }
15533     if (pausing) {
15534         DisplayError(_("You can't revert while pausing"), 0);
15535         return;
15536     }
15537     SendToICS(ics_prefix);
15538     SendToICS("revert\n");
15539 }
15540
15541 void
15542 RetractMoveEvent ()
15543 {
15544     switch (gameMode) {
15545       case MachinePlaysWhite:
15546       case MachinePlaysBlack:
15547         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15548             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15549             return;
15550         }
15551         if (forwardMostMove < 2) return;
15552         currentMove = forwardMostMove = forwardMostMove - 2;
15553         whiteTimeRemaining = timeRemaining[0][currentMove];
15554         blackTimeRemaining = timeRemaining[1][currentMove];
15555         DisplayBothClocks();
15556         DisplayMove(currentMove - 1);
15557         ClearHighlights();/*!! could figure this out*/
15558         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15559         SendToProgram("remove\n", &first);
15560         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15561         break;
15562
15563       case BeginningOfGame:
15564       default:
15565         break;
15566
15567       case IcsPlayingWhite:
15568       case IcsPlayingBlack:
15569         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15570             SendToICS(ics_prefix);
15571             SendToICS("takeback 2\n");
15572         } else {
15573             SendToICS(ics_prefix);
15574             SendToICS("takeback 1\n");
15575         }
15576         break;
15577     }
15578 }
15579
15580 void
15581 MoveNowEvent ()
15582 {
15583     ChessProgramState *cps;
15584
15585     switch (gameMode) {
15586       case MachinePlaysWhite:
15587         if (!WhiteOnMove(forwardMostMove)) {
15588             DisplayError(_("It is your turn"), 0);
15589             return;
15590         }
15591         cps = &first;
15592         break;
15593       case MachinePlaysBlack:
15594         if (WhiteOnMove(forwardMostMove)) {
15595             DisplayError(_("It is your turn"), 0);
15596             return;
15597         }
15598         cps = &first;
15599         break;
15600       case TwoMachinesPlay:
15601         if (WhiteOnMove(forwardMostMove) ==
15602             (first.twoMachinesColor[0] == 'w')) {
15603             cps = &first;
15604         } else {
15605             cps = &second;
15606         }
15607         break;
15608       case BeginningOfGame:
15609       default:
15610         return;
15611     }
15612     SendToProgram("?\n", cps);
15613 }
15614
15615 void
15616 TruncateGameEvent ()
15617 {
15618     EditGameEvent();
15619     if (gameMode != EditGame) return;
15620     TruncateGame();
15621 }
15622
15623 void
15624 TruncateGame ()
15625 {
15626     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15627     if (forwardMostMove > currentMove) {
15628         if (gameInfo.resultDetails != NULL) {
15629             free(gameInfo.resultDetails);
15630             gameInfo.resultDetails = NULL;
15631             gameInfo.result = GameUnfinished;
15632         }
15633         forwardMostMove = currentMove;
15634         HistorySet(parseList, backwardMostMove, forwardMostMove,
15635                    currentMove-1);
15636     }
15637 }
15638
15639 void
15640 HintEvent ()
15641 {
15642     if (appData.noChessProgram) return;
15643     switch (gameMode) {
15644       case MachinePlaysWhite:
15645         if (WhiteOnMove(forwardMostMove)) {
15646             DisplayError(_("Wait until your turn."), 0);
15647             return;
15648         }
15649         break;
15650       case BeginningOfGame:
15651       case MachinePlaysBlack:
15652         if (!WhiteOnMove(forwardMostMove)) {
15653             DisplayError(_("Wait until your turn."), 0);
15654             return;
15655         }
15656         break;
15657       default:
15658         DisplayError(_("No hint available"), 0);
15659         return;
15660     }
15661     SendToProgram("hint\n", &first);
15662     hintRequested = TRUE;
15663 }
15664
15665 void
15666 CreateBookEvent ()
15667 {
15668     ListGame * lg = (ListGame *) gameList.head;
15669     FILE *f, *g;
15670     int nItem;
15671     static int secondTime = FALSE;
15672
15673     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15674         DisplayError(_("Game list not loaded or empty"), 0);
15675         return;
15676     }
15677
15678     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15679         fclose(g);
15680         secondTime++;
15681         DisplayNote(_("Book file exists! Try again for overwrite."));
15682         return;
15683     }
15684
15685     creatingBook = TRUE;
15686     secondTime = FALSE;
15687
15688     /* Get list size */
15689     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15690         LoadGame(f, nItem, "", TRUE);
15691         AddGameToBook(TRUE);
15692         lg = (ListGame *) lg->node.succ;
15693     }
15694
15695     creatingBook = FALSE;
15696     FlushBook();
15697 }
15698
15699 void
15700 BookEvent ()
15701 {
15702     if (appData.noChessProgram) return;
15703     switch (gameMode) {
15704       case MachinePlaysWhite:
15705         if (WhiteOnMove(forwardMostMove)) {
15706             DisplayError(_("Wait until your turn."), 0);
15707             return;
15708         }
15709         break;
15710       case BeginningOfGame:
15711       case MachinePlaysBlack:
15712         if (!WhiteOnMove(forwardMostMove)) {
15713             DisplayError(_("Wait until your turn."), 0);
15714             return;
15715         }
15716         break;
15717       case EditPosition:
15718         EditPositionDone(TRUE);
15719         break;
15720       case TwoMachinesPlay:
15721         return;
15722       default:
15723         break;
15724     }
15725     SendToProgram("bk\n", &first);
15726     bookOutput[0] = NULLCHAR;
15727     bookRequested = TRUE;
15728 }
15729
15730 void
15731 AboutGameEvent ()
15732 {
15733     char *tags = PGNTags(&gameInfo);
15734     TagsPopUp(tags, CmailMsg());
15735     free(tags);
15736 }
15737
15738 /* end button procedures */
15739
15740 void
15741 PrintPosition (FILE *fp, int move)
15742 {
15743     int i, j;
15744
15745     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15746         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15747             char c = PieceToChar(boards[move][i][j]);
15748             fputc(c == 'x' ? '.' : c, fp);
15749             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15750         }
15751     }
15752     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15753       fprintf(fp, "white to play\n");
15754     else
15755       fprintf(fp, "black to play\n");
15756 }
15757
15758 void
15759 PrintOpponents (FILE *fp)
15760 {
15761     if (gameInfo.white != NULL) {
15762         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15763     } else {
15764         fprintf(fp, "\n");
15765     }
15766 }
15767
15768 /* Find last component of program's own name, using some heuristics */
15769 void
15770 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15771 {
15772     char *p, *q, c;
15773     int local = (strcmp(host, "localhost") == 0);
15774     while (!local && (p = strchr(prog, ';')) != NULL) {
15775         p++;
15776         while (*p == ' ') p++;
15777         prog = p;
15778     }
15779     if (*prog == '"' || *prog == '\'') {
15780         q = strchr(prog + 1, *prog);
15781     } else {
15782         q = strchr(prog, ' ');
15783     }
15784     if (q == NULL) q = prog + strlen(prog);
15785     p = q;
15786     while (p >= prog && *p != '/' && *p != '\\') p--;
15787     p++;
15788     if(p == prog && *p == '"') p++;
15789     c = *q; *q = 0;
15790     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15791     memcpy(buf, p, q - p);
15792     buf[q - p] = NULLCHAR;
15793     if (!local) {
15794         strcat(buf, "@");
15795         strcat(buf, host);
15796     }
15797 }
15798
15799 char *
15800 TimeControlTagValue ()
15801 {
15802     char buf[MSG_SIZ];
15803     if (!appData.clockMode) {
15804       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15805     } else if (movesPerSession > 0) {
15806       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15807     } else if (timeIncrement == 0) {
15808       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15809     } else {
15810       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15811     }
15812     return StrSave(buf);
15813 }
15814
15815 void
15816 SetGameInfo ()
15817 {
15818     /* This routine is used only for certain modes */
15819     VariantClass v = gameInfo.variant;
15820     ChessMove r = GameUnfinished;
15821     char *p = NULL;
15822
15823     if(keepInfo) return;
15824
15825     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15826         r = gameInfo.result;
15827         p = gameInfo.resultDetails;
15828         gameInfo.resultDetails = NULL;
15829     }
15830     ClearGameInfo(&gameInfo);
15831     gameInfo.variant = v;
15832
15833     switch (gameMode) {
15834       case MachinePlaysWhite:
15835         gameInfo.event = StrSave( appData.pgnEventHeader );
15836         gameInfo.site = StrSave(HostName());
15837         gameInfo.date = PGNDate();
15838         gameInfo.round = StrSave("-");
15839         gameInfo.white = StrSave(first.tidy);
15840         gameInfo.black = StrSave(UserName());
15841         gameInfo.timeControl = TimeControlTagValue();
15842         break;
15843
15844       case MachinePlaysBlack:
15845         gameInfo.event = StrSave( appData.pgnEventHeader );
15846         gameInfo.site = StrSave(HostName());
15847         gameInfo.date = PGNDate();
15848         gameInfo.round = StrSave("-");
15849         gameInfo.white = StrSave(UserName());
15850         gameInfo.black = StrSave(first.tidy);
15851         gameInfo.timeControl = TimeControlTagValue();
15852         break;
15853
15854       case TwoMachinesPlay:
15855         gameInfo.event = StrSave( appData.pgnEventHeader );
15856         gameInfo.site = StrSave(HostName());
15857         gameInfo.date = PGNDate();
15858         if (roundNr > 0) {
15859             char buf[MSG_SIZ];
15860             snprintf(buf, MSG_SIZ, "%d", roundNr);
15861             gameInfo.round = StrSave(buf);
15862         } else {
15863             gameInfo.round = StrSave("-");
15864         }
15865         if (first.twoMachinesColor[0] == 'w') {
15866             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15867             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15868         } else {
15869             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15870             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15871         }
15872         gameInfo.timeControl = TimeControlTagValue();
15873         break;
15874
15875       case EditGame:
15876         gameInfo.event = StrSave("Edited game");
15877         gameInfo.site = StrSave(HostName());
15878         gameInfo.date = PGNDate();
15879         gameInfo.round = StrSave("-");
15880         gameInfo.white = StrSave("-");
15881         gameInfo.black = StrSave("-");
15882         gameInfo.result = r;
15883         gameInfo.resultDetails = p;
15884         break;
15885
15886       case EditPosition:
15887         gameInfo.event = StrSave("Edited position");
15888         gameInfo.site = StrSave(HostName());
15889         gameInfo.date = PGNDate();
15890         gameInfo.round = StrSave("-");
15891         gameInfo.white = StrSave("-");
15892         gameInfo.black = StrSave("-");
15893         break;
15894
15895       case IcsPlayingWhite:
15896       case IcsPlayingBlack:
15897       case IcsObserving:
15898       case IcsExamining:
15899         break;
15900
15901       case PlayFromGameFile:
15902         gameInfo.event = StrSave("Game from non-PGN file");
15903         gameInfo.site = StrSave(HostName());
15904         gameInfo.date = PGNDate();
15905         gameInfo.round = StrSave("-");
15906         gameInfo.white = StrSave("?");
15907         gameInfo.black = StrSave("?");
15908         break;
15909
15910       default:
15911         break;
15912     }
15913 }
15914
15915 void
15916 ReplaceComment (int index, char *text)
15917 {
15918     int len;
15919     char *p;
15920     float score;
15921
15922     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15923        pvInfoList[index-1].depth == len &&
15924        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15925        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15926     while (*text == '\n') text++;
15927     len = strlen(text);
15928     while (len > 0 && text[len - 1] == '\n') len--;
15929
15930     if (commentList[index] != NULL)
15931       free(commentList[index]);
15932
15933     if (len == 0) {
15934         commentList[index] = NULL;
15935         return;
15936     }
15937   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15938       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15939       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15940     commentList[index] = (char *) malloc(len + 2);
15941     strncpy(commentList[index], text, len);
15942     commentList[index][len] = '\n';
15943     commentList[index][len + 1] = NULLCHAR;
15944   } else {
15945     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15946     char *p;
15947     commentList[index] = (char *) malloc(len + 7);
15948     safeStrCpy(commentList[index], "{\n", 3);
15949     safeStrCpy(commentList[index]+2, text, len+1);
15950     commentList[index][len+2] = NULLCHAR;
15951     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15952     strcat(commentList[index], "\n}\n");
15953   }
15954 }
15955
15956 void
15957 CrushCRs (char *text)
15958 {
15959   char *p = text;
15960   char *q = text;
15961   char ch;
15962
15963   do {
15964     ch = *p++;
15965     if (ch == '\r') continue;
15966     *q++ = ch;
15967   } while (ch != '\0');
15968 }
15969
15970 void
15971 AppendComment (int index, char *text, Boolean addBraces)
15972 /* addBraces  tells if we should add {} */
15973 {
15974     int oldlen, len;
15975     char *old;
15976
15977 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15978     if(addBraces == 3) addBraces = 0; else // force appending literally
15979     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15980
15981     CrushCRs(text);
15982     while (*text == '\n') text++;
15983     len = strlen(text);
15984     while (len > 0 && text[len - 1] == '\n') len--;
15985     text[len] = NULLCHAR;
15986
15987     if (len == 0) return;
15988
15989     if (commentList[index] != NULL) {
15990       Boolean addClosingBrace = addBraces;
15991         old = commentList[index];
15992         oldlen = strlen(old);
15993         while(commentList[index][oldlen-1] ==  '\n')
15994           commentList[index][--oldlen] = NULLCHAR;
15995         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15996         safeStrCpy(commentList[index], old, oldlen + len + 6);
15997         free(old);
15998         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15999         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16000           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16001           while (*text == '\n') { text++; len--; }
16002           commentList[index][--oldlen] = NULLCHAR;
16003       }
16004         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16005         else          strcat(commentList[index], "\n");
16006         strcat(commentList[index], text);
16007         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16008         else          strcat(commentList[index], "\n");
16009     } else {
16010         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16011         if(addBraces)
16012           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16013         else commentList[index][0] = NULLCHAR;
16014         strcat(commentList[index], text);
16015         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16016         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16017     }
16018 }
16019
16020 static char *
16021 FindStr (char * text, char * sub_text)
16022 {
16023     char * result = strstr( text, sub_text );
16024
16025     if( result != NULL ) {
16026         result += strlen( sub_text );
16027     }
16028
16029     return result;
16030 }
16031
16032 /* [AS] Try to extract PV info from PGN comment */
16033 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16034 char *
16035 GetInfoFromComment (int index, char * text)
16036 {
16037     char * sep = text, *p;
16038
16039     if( text != NULL && index > 0 ) {
16040         int score = 0;
16041         int depth = 0;
16042         int time = -1, sec = 0, deci;
16043         char * s_eval = FindStr( text, "[%eval " );
16044         char * s_emt = FindStr( text, "[%emt " );
16045 #if 0
16046         if( s_eval != NULL || s_emt != NULL ) {
16047 #else
16048         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16049 #endif
16050             /* New style */
16051             char delim;
16052
16053             if( s_eval != NULL ) {
16054                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16055                     return text;
16056                 }
16057
16058                 if( delim != ']' ) {
16059                     return text;
16060                 }
16061             }
16062
16063             if( s_emt != NULL ) {
16064             }
16065                 return text;
16066         }
16067         else {
16068             /* We expect something like: [+|-]nnn.nn/dd */
16069             int score_lo = 0;
16070
16071             if(*text != '{') return text; // [HGM] braces: must be normal comment
16072
16073             sep = strchr( text, '/' );
16074             if( sep == NULL || sep < (text+4) ) {
16075                 return text;
16076             }
16077
16078             p = text;
16079             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16080             if(p[1] == '(') { // comment starts with PV
16081                p = strchr(p, ')'); // locate end of PV
16082                if(p == NULL || sep < p+5) return text;
16083                // at this point we have something like "{(.*) +0.23/6 ..."
16084                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16085                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16086                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16087             }
16088             time = -1; sec = -1; deci = -1;
16089             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16090                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16091                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16092                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16093                 return text;
16094             }
16095
16096             if( score_lo < 0 || score_lo >= 100 ) {
16097                 return text;
16098             }
16099
16100             if(sec >= 0) time = 600*time + 10*sec; else
16101             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16102
16103             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16104
16105             /* [HGM] PV time: now locate end of PV info */
16106             while( *++sep >= '0' && *sep <= '9'); // strip depth
16107             if(time >= 0)
16108             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16109             if(sec >= 0)
16110             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16111             if(deci >= 0)
16112             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16113             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16114         }
16115
16116         if( depth <= 0 ) {
16117             return text;
16118         }
16119
16120         if( time < 0 ) {
16121             time = -1;
16122         }
16123
16124         pvInfoList[index-1].depth = depth;
16125         pvInfoList[index-1].score = score;
16126         pvInfoList[index-1].time  = 10*time; // centi-sec
16127         if(*sep == '}') *sep = 0; else *--sep = '{';
16128         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16129     }
16130     return sep;
16131 }
16132
16133 void
16134 SendToProgram (char *message, ChessProgramState *cps)
16135 {
16136     int count, outCount, error;
16137     char buf[MSG_SIZ];
16138
16139     if (cps->pr == NoProc) return;
16140     Attention(cps);
16141
16142     if (appData.debugMode) {
16143         TimeMark now;
16144         GetTimeMark(&now);
16145         fprintf(debugFP, "%ld >%-6s: %s",
16146                 SubtractTimeMarks(&now, &programStartTime),
16147                 cps->which, message);
16148         if(serverFP)
16149             fprintf(serverFP, "%ld >%-6s: %s",
16150                 SubtractTimeMarks(&now, &programStartTime),
16151                 cps->which, message), fflush(serverFP);
16152     }
16153
16154     count = strlen(message);
16155     outCount = OutputToProcess(cps->pr, message, count, &error);
16156     if (outCount < count && !exiting
16157                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16158       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16159       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16160         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16161             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16162                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16163                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16164                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16165             } else {
16166                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16167                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16168                 gameInfo.result = res;
16169             }
16170             gameInfo.resultDetails = StrSave(buf);
16171         }
16172         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16173         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16174     }
16175 }
16176
16177 void
16178 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16179 {
16180     char *end_str;
16181     char buf[MSG_SIZ];
16182     ChessProgramState *cps = (ChessProgramState *)closure;
16183
16184     if (isr != cps->isr) return; /* Killed intentionally */
16185     if (count <= 0) {
16186         if (count == 0) {
16187             RemoveInputSource(cps->isr);
16188             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16189                     _(cps->which), cps->program);
16190             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16191             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16192                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16193                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16194                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16195                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16196                 } else {
16197                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16198                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16199                     gameInfo.result = res;
16200                 }
16201                 gameInfo.resultDetails = StrSave(buf);
16202             }
16203             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16204             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16205         } else {
16206             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16207                     _(cps->which), cps->program);
16208             RemoveInputSource(cps->isr);
16209
16210             /* [AS] Program is misbehaving badly... kill it */
16211             if( count == -2 ) {
16212                 DestroyChildProcess( cps->pr, 9 );
16213                 cps->pr = NoProc;
16214             }
16215
16216             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16217         }
16218         return;
16219     }
16220
16221     if ((end_str = strchr(message, '\r')) != NULL)
16222       *end_str = NULLCHAR;
16223     if ((end_str = strchr(message, '\n')) != NULL)
16224       *end_str = NULLCHAR;
16225
16226     if (appData.debugMode) {
16227         TimeMark now; int print = 1;
16228         char *quote = ""; char c; int i;
16229
16230         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16231                 char start = message[0];
16232                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16233                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16234                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16235                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16236                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16237                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16238                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16239                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16240                    sscanf(message, "hint: %c", &c)!=1 &&
16241                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16242                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16243                     print = (appData.engineComments >= 2);
16244                 }
16245                 message[0] = start; // restore original message
16246         }
16247         if(print) {
16248                 GetTimeMark(&now);
16249                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16250                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16251                         quote,
16252                         message);
16253                 if(serverFP)
16254                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16255                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16256                         quote,
16257                         message), fflush(serverFP);
16258         }
16259     }
16260
16261     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16262     if (appData.icsEngineAnalyze) {
16263         if (strstr(message, "whisper") != NULL ||
16264              strstr(message, "kibitz") != NULL ||
16265             strstr(message, "tellics") != NULL) return;
16266     }
16267
16268     HandleMachineMove(message, cps);
16269 }
16270
16271
16272 void
16273 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16274 {
16275     char buf[MSG_SIZ];
16276     int seconds;
16277
16278     if( timeControl_2 > 0 ) {
16279         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16280             tc = timeControl_2;
16281         }
16282     }
16283     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16284     inc /= cps->timeOdds;
16285     st  /= cps->timeOdds;
16286
16287     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16288
16289     if (st > 0) {
16290       /* Set exact time per move, normally using st command */
16291       if (cps->stKludge) {
16292         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16293         seconds = st % 60;
16294         if (seconds == 0) {
16295           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16296         } else {
16297           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16298         }
16299       } else {
16300         snprintf(buf, MSG_SIZ, "st %d\n", st);
16301       }
16302     } else {
16303       /* Set conventional or incremental time control, using level command */
16304       if (seconds == 0) {
16305         /* Note old gnuchess bug -- minutes:seconds used to not work.
16306            Fixed in later versions, but still avoid :seconds
16307            when seconds is 0. */
16308         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16309       } else {
16310         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16311                  seconds, inc/1000.);
16312       }
16313     }
16314     SendToProgram(buf, cps);
16315
16316     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16317     /* Orthogonally, limit search to given depth */
16318     if (sd > 0) {
16319       if (cps->sdKludge) {
16320         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16321       } else {
16322         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16323       }
16324       SendToProgram(buf, cps);
16325     }
16326
16327     if(cps->nps >= 0) { /* [HGM] nps */
16328         if(cps->supportsNPS == FALSE)
16329           cps->nps = -1; // don't use if engine explicitly says not supported!
16330         else {
16331           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16332           SendToProgram(buf, cps);
16333         }
16334     }
16335 }
16336
16337 ChessProgramState *
16338 WhitePlayer ()
16339 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16340 {
16341     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16342        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16343         return &second;
16344     return &first;
16345 }
16346
16347 void
16348 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16349 {
16350     char message[MSG_SIZ];
16351     long time, otime;
16352
16353     /* Note: this routine must be called when the clocks are stopped
16354        or when they have *just* been set or switched; otherwise
16355        it will be off by the time since the current tick started.
16356     */
16357     if (machineWhite) {
16358         time = whiteTimeRemaining / 10;
16359         otime = blackTimeRemaining / 10;
16360     } else {
16361         time = blackTimeRemaining / 10;
16362         otime = whiteTimeRemaining / 10;
16363     }
16364     /* [HGM] translate opponent's time by time-odds factor */
16365     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16366
16367     if (time <= 0) time = 1;
16368     if (otime <= 0) otime = 1;
16369
16370     snprintf(message, MSG_SIZ, "time %ld\n", time);
16371     SendToProgram(message, cps);
16372
16373     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16374     SendToProgram(message, cps);
16375 }
16376
16377 char *
16378 EngineDefinedVariant (ChessProgramState *cps, int n)
16379 {   // return name of n-th unknown variant that engine supports
16380     static char buf[MSG_SIZ];
16381     char *p, *s = cps->variants;
16382     if(!s) return NULL;
16383     do { // parse string from variants feature
16384       VariantClass v;
16385         p = strchr(s, ',');
16386         if(p) *p = NULLCHAR;
16387       v = StringToVariant(s);
16388       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16389         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16390             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16391         }
16392         if(p) *p++ = ',';
16393         if(n < 0) return buf;
16394     } while(s = p);
16395     return NULL;
16396 }
16397
16398 int
16399 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16400 {
16401   char buf[MSG_SIZ];
16402   int len = strlen(name);
16403   int val;
16404
16405   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16406     (*p) += len + 1;
16407     sscanf(*p, "%d", &val);
16408     *loc = (val != 0);
16409     while (**p && **p != ' ')
16410       (*p)++;
16411     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16412     SendToProgram(buf, cps);
16413     return TRUE;
16414   }
16415   return FALSE;
16416 }
16417
16418 int
16419 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16420 {
16421   char buf[MSG_SIZ];
16422   int len = strlen(name);
16423   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16424     (*p) += len + 1;
16425     sscanf(*p, "%d", loc);
16426     while (**p && **p != ' ') (*p)++;
16427     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16428     SendToProgram(buf, cps);
16429     return TRUE;
16430   }
16431   return FALSE;
16432 }
16433
16434 int
16435 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16436 {
16437   char buf[MSG_SIZ];
16438   int len = strlen(name);
16439   if (strncmp((*p), name, len) == 0
16440       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16441     (*p) += len + 2;
16442     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16443     sscanf(*p, "%[^\"]", *loc);
16444     while (**p && **p != '\"') (*p)++;
16445     if (**p == '\"') (*p)++;
16446     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16447     SendToProgram(buf, cps);
16448     return TRUE;
16449   }
16450   return FALSE;
16451 }
16452
16453 int
16454 ParseOption (Option *opt, ChessProgramState *cps)
16455 // [HGM] options: process the string that defines an engine option, and determine
16456 // name, type, default value, and allowed value range
16457 {
16458         char *p, *q, buf[MSG_SIZ];
16459         int n, min = (-1)<<31, max = 1<<31, def;
16460
16461         if(p = strstr(opt->name, " -spin ")) {
16462             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16463             if(max < min) max = min; // enforce consistency
16464             if(def < min) def = min;
16465             if(def > max) def = max;
16466             opt->value = def;
16467             opt->min = min;
16468             opt->max = max;
16469             opt->type = Spin;
16470         } else if((p = strstr(opt->name, " -slider "))) {
16471             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16472             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16473             if(max < min) max = min; // enforce consistency
16474             if(def < min) def = min;
16475             if(def > max) def = max;
16476             opt->value = def;
16477             opt->min = min;
16478             opt->max = max;
16479             opt->type = Spin; // Slider;
16480         } else if((p = strstr(opt->name, " -string "))) {
16481             opt->textValue = p+9;
16482             opt->type = TextBox;
16483         } else if((p = strstr(opt->name, " -file "))) {
16484             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16485             opt->textValue = p+7;
16486             opt->type = FileName; // FileName;
16487         } else if((p = strstr(opt->name, " -path "))) {
16488             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16489             opt->textValue = p+7;
16490             opt->type = PathName; // PathName;
16491         } else if(p = strstr(opt->name, " -check ")) {
16492             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16493             opt->value = (def != 0);
16494             opt->type = CheckBox;
16495         } else if(p = strstr(opt->name, " -combo ")) {
16496             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16497             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16498             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16499             opt->value = n = 0;
16500             while(q = StrStr(q, " /// ")) {
16501                 n++; *q = 0;    // count choices, and null-terminate each of them
16502                 q += 5;
16503                 if(*q == '*') { // remember default, which is marked with * prefix
16504                     q++;
16505                     opt->value = n;
16506                 }
16507                 cps->comboList[cps->comboCnt++] = q;
16508             }
16509             cps->comboList[cps->comboCnt++] = NULL;
16510             opt->max = n + 1;
16511             opt->type = ComboBox;
16512         } else if(p = strstr(opt->name, " -button")) {
16513             opt->type = Button;
16514         } else if(p = strstr(opt->name, " -save")) {
16515             opt->type = SaveButton;
16516         } else return FALSE;
16517         *p = 0; // terminate option name
16518         // now look if the command-line options define a setting for this engine option.
16519         if(cps->optionSettings && cps->optionSettings[0])
16520             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16521         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16522           snprintf(buf, MSG_SIZ, "option %s", p);
16523                 if(p = strstr(buf, ",")) *p = 0;
16524                 if(q = strchr(buf, '=')) switch(opt->type) {
16525                     case ComboBox:
16526                         for(n=0; n<opt->max; n++)
16527                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16528                         break;
16529                     case TextBox:
16530                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16531                         break;
16532                     case Spin:
16533                     case CheckBox:
16534                         opt->value = atoi(q+1);
16535                     default:
16536                         break;
16537                 }
16538                 strcat(buf, "\n");
16539                 SendToProgram(buf, cps);
16540         }
16541         return TRUE;
16542 }
16543
16544 void
16545 FeatureDone (ChessProgramState *cps, int val)
16546 {
16547   DelayedEventCallback cb = GetDelayedEvent();
16548   if ((cb == InitBackEnd3 && cps == &first) ||
16549       (cb == SettingsMenuIfReady && cps == &second) ||
16550       (cb == LoadEngine) ||
16551       (cb == TwoMachinesEventIfReady)) {
16552     CancelDelayedEvent();
16553     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16554   }
16555   cps->initDone = val;
16556   if(val) cps->reload = FALSE;
16557 }
16558
16559 /* Parse feature command from engine */
16560 void
16561 ParseFeatures (char *args, ChessProgramState *cps)
16562 {
16563   char *p = args;
16564   char *q = NULL;
16565   int val;
16566   char buf[MSG_SIZ];
16567
16568   for (;;) {
16569     while (*p == ' ') p++;
16570     if (*p == NULLCHAR) return;
16571
16572     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16573     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16574     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16575     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16576     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16577     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16578     if (BoolFeature(&p, "reuse", &val, cps)) {
16579       /* Engine can disable reuse, but can't enable it if user said no */
16580       if (!val) cps->reuse = FALSE;
16581       continue;
16582     }
16583     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16584     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16585       if (gameMode == TwoMachinesPlay) {
16586         DisplayTwoMachinesTitle();
16587       } else {
16588         DisplayTitle("");
16589       }
16590       continue;
16591     }
16592     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16593     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16594     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16595     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16596     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16597     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16598     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16599     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16600     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16601     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16602     if (IntFeature(&p, "done", &val, cps)) {
16603       FeatureDone(cps, val);
16604       continue;
16605     }
16606     /* Added by Tord: */
16607     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16608     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16609     /* End of additions by Tord */
16610
16611     /* [HGM] added features: */
16612     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16613     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16614     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16615     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16616     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16617     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16618     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16619     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16620         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16621         FREE(cps->option[cps->nrOptions].name);
16622         cps->option[cps->nrOptions].name = q; q = NULL;
16623         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16624           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16625             SendToProgram(buf, cps);
16626             continue;
16627         }
16628         if(cps->nrOptions >= MAX_OPTIONS) {
16629             cps->nrOptions--;
16630             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16631             DisplayError(buf, 0);
16632         }
16633         continue;
16634     }
16635     /* End of additions by HGM */
16636
16637     /* unknown feature: complain and skip */
16638     q = p;
16639     while (*q && *q != '=') q++;
16640     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16641     SendToProgram(buf, cps);
16642     p = q;
16643     if (*p == '=') {
16644       p++;
16645       if (*p == '\"') {
16646         p++;
16647         while (*p && *p != '\"') p++;
16648         if (*p == '\"') p++;
16649       } else {
16650         while (*p && *p != ' ') p++;
16651       }
16652     }
16653   }
16654
16655 }
16656
16657 void
16658 PeriodicUpdatesEvent (int newState)
16659 {
16660     if (newState == appData.periodicUpdates)
16661       return;
16662
16663     appData.periodicUpdates=newState;
16664
16665     /* Display type changes, so update it now */
16666 //    DisplayAnalysis();
16667
16668     /* Get the ball rolling again... */
16669     if (newState) {
16670         AnalysisPeriodicEvent(1);
16671         StartAnalysisClock();
16672     }
16673 }
16674
16675 void
16676 PonderNextMoveEvent (int newState)
16677 {
16678     if (newState == appData.ponderNextMove) return;
16679     if (gameMode == EditPosition) EditPositionDone(TRUE);
16680     if (newState) {
16681         SendToProgram("hard\n", &first);
16682         if (gameMode == TwoMachinesPlay) {
16683             SendToProgram("hard\n", &second);
16684         }
16685     } else {
16686         SendToProgram("easy\n", &first);
16687         thinkOutput[0] = NULLCHAR;
16688         if (gameMode == TwoMachinesPlay) {
16689             SendToProgram("easy\n", &second);
16690         }
16691     }
16692     appData.ponderNextMove = newState;
16693 }
16694
16695 void
16696 NewSettingEvent (int option, int *feature, char *command, int value)
16697 {
16698     char buf[MSG_SIZ];
16699
16700     if (gameMode == EditPosition) EditPositionDone(TRUE);
16701     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16702     if(feature == NULL || *feature) SendToProgram(buf, &first);
16703     if (gameMode == TwoMachinesPlay) {
16704         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16705     }
16706 }
16707
16708 void
16709 ShowThinkingEvent ()
16710 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16711 {
16712     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16713     int newState = appData.showThinking
16714         // [HGM] thinking: other features now need thinking output as well
16715         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16716
16717     if (oldState == newState) return;
16718     oldState = newState;
16719     if (gameMode == EditPosition) EditPositionDone(TRUE);
16720     if (oldState) {
16721         SendToProgram("post\n", &first);
16722         if (gameMode == TwoMachinesPlay) {
16723             SendToProgram("post\n", &second);
16724         }
16725     } else {
16726         SendToProgram("nopost\n", &first);
16727         thinkOutput[0] = NULLCHAR;
16728         if (gameMode == TwoMachinesPlay) {
16729             SendToProgram("nopost\n", &second);
16730         }
16731     }
16732 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16733 }
16734
16735 void
16736 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16737 {
16738   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16739   if (pr == NoProc) return;
16740   AskQuestion(title, question, replyPrefix, pr);
16741 }
16742
16743 void
16744 TypeInEvent (char firstChar)
16745 {
16746     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16747         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16748         gameMode == AnalyzeMode || gameMode == EditGame ||
16749         gameMode == EditPosition || gameMode == IcsExamining ||
16750         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16751         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16752                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16753                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16754         gameMode == Training) PopUpMoveDialog(firstChar);
16755 }
16756
16757 void
16758 TypeInDoneEvent (char *move)
16759 {
16760         Board board;
16761         int n, fromX, fromY, toX, toY;
16762         char promoChar;
16763         ChessMove moveType;
16764
16765         // [HGM] FENedit
16766         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16767                 EditPositionPasteFEN(move);
16768                 return;
16769         }
16770         // [HGM] movenum: allow move number to be typed in any mode
16771         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16772           ToNrEvent(2*n-1);
16773           return;
16774         }
16775         // undocumented kludge: allow command-line option to be typed in!
16776         // (potentially fatal, and does not implement the effect of the option.)
16777         // should only be used for options that are values on which future decisions will be made,
16778         // and definitely not on options that would be used during initialization.
16779         if(strstr(move, "!!! -") == move) {
16780             ParseArgsFromString(move+4);
16781             return;
16782         }
16783
16784       if (gameMode != EditGame && currentMove != forwardMostMove &&
16785         gameMode != Training) {
16786         DisplayMoveError(_("Displayed move is not current"));
16787       } else {
16788         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16789           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16790         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16791         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16792           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16793           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16794         } else {
16795           DisplayMoveError(_("Could not parse move"));
16796         }
16797       }
16798 }
16799
16800 void
16801 DisplayMove (int moveNumber)
16802 {
16803     char message[MSG_SIZ];
16804     char res[MSG_SIZ];
16805     char cpThinkOutput[MSG_SIZ];
16806
16807     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16808
16809     if (moveNumber == forwardMostMove - 1 ||
16810         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16811
16812         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16813
16814         if (strchr(cpThinkOutput, '\n')) {
16815             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16816         }
16817     } else {
16818         *cpThinkOutput = NULLCHAR;
16819     }
16820
16821     /* [AS] Hide thinking from human user */
16822     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16823         *cpThinkOutput = NULLCHAR;
16824         if( thinkOutput[0] != NULLCHAR ) {
16825             int i;
16826
16827             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16828                 cpThinkOutput[i] = '.';
16829             }
16830             cpThinkOutput[i] = NULLCHAR;
16831             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16832         }
16833     }
16834
16835     if (moveNumber == forwardMostMove - 1 &&
16836         gameInfo.resultDetails != NULL) {
16837         if (gameInfo.resultDetails[0] == NULLCHAR) {
16838           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16839         } else {
16840           snprintf(res, MSG_SIZ, " {%s} %s",
16841                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16842         }
16843     } else {
16844         res[0] = NULLCHAR;
16845     }
16846
16847     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16848         DisplayMessage(res, cpThinkOutput);
16849     } else {
16850       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16851                 WhiteOnMove(moveNumber) ? " " : ".. ",
16852                 parseList[moveNumber], res);
16853         DisplayMessage(message, cpThinkOutput);
16854     }
16855 }
16856
16857 void
16858 DisplayComment (int moveNumber, char *text)
16859 {
16860     char title[MSG_SIZ];
16861
16862     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16863       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16864     } else {
16865       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16866               WhiteOnMove(moveNumber) ? " " : ".. ",
16867               parseList[moveNumber]);
16868     }
16869     if (text != NULL && (appData.autoDisplayComment || commentUp))
16870         CommentPopUp(title, text);
16871 }
16872
16873 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16874  * might be busy thinking or pondering.  It can be omitted if your
16875  * gnuchess is configured to stop thinking immediately on any user
16876  * input.  However, that gnuchess feature depends on the FIONREAD
16877  * ioctl, which does not work properly on some flavors of Unix.
16878  */
16879 void
16880 Attention (ChessProgramState *cps)
16881 {
16882 #if ATTENTION
16883     if (!cps->useSigint) return;
16884     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16885     switch (gameMode) {
16886       case MachinePlaysWhite:
16887       case MachinePlaysBlack:
16888       case TwoMachinesPlay:
16889       case IcsPlayingWhite:
16890       case IcsPlayingBlack:
16891       case AnalyzeMode:
16892       case AnalyzeFile:
16893         /* Skip if we know it isn't thinking */
16894         if (!cps->maybeThinking) return;
16895         if (appData.debugMode)
16896           fprintf(debugFP, "Interrupting %s\n", cps->which);
16897         InterruptChildProcess(cps->pr);
16898         cps->maybeThinking = FALSE;
16899         break;
16900       default:
16901         break;
16902     }
16903 #endif /*ATTENTION*/
16904 }
16905
16906 int
16907 CheckFlags ()
16908 {
16909     if (whiteTimeRemaining <= 0) {
16910         if (!whiteFlag) {
16911             whiteFlag = TRUE;
16912             if (appData.icsActive) {
16913                 if (appData.autoCallFlag &&
16914                     gameMode == IcsPlayingBlack && !blackFlag) {
16915                   SendToICS(ics_prefix);
16916                   SendToICS("flag\n");
16917                 }
16918             } else {
16919                 if (blackFlag) {
16920                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16921                 } else {
16922                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16923                     if (appData.autoCallFlag) {
16924                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16925                         return TRUE;
16926                     }
16927                 }
16928             }
16929         }
16930     }
16931     if (blackTimeRemaining <= 0) {
16932         if (!blackFlag) {
16933             blackFlag = TRUE;
16934             if (appData.icsActive) {
16935                 if (appData.autoCallFlag &&
16936                     gameMode == IcsPlayingWhite && !whiteFlag) {
16937                   SendToICS(ics_prefix);
16938                   SendToICS("flag\n");
16939                 }
16940             } else {
16941                 if (whiteFlag) {
16942                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16943                 } else {
16944                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16945                     if (appData.autoCallFlag) {
16946                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16947                         return TRUE;
16948                     }
16949                 }
16950             }
16951         }
16952     }
16953     return FALSE;
16954 }
16955
16956 void
16957 CheckTimeControl ()
16958 {
16959     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16960         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16961
16962     /*
16963      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16964      */
16965     if ( !WhiteOnMove(forwardMostMove) ) {
16966         /* White made time control */
16967         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16968         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16969         /* [HGM] time odds: correct new time quota for time odds! */
16970                                             / WhitePlayer()->timeOdds;
16971         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16972     } else {
16973         lastBlack -= blackTimeRemaining;
16974         /* Black made time control */
16975         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16976                                             / WhitePlayer()->other->timeOdds;
16977         lastWhite = whiteTimeRemaining;
16978     }
16979 }
16980
16981 void
16982 DisplayBothClocks ()
16983 {
16984     int wom = gameMode == EditPosition ?
16985       !blackPlaysFirst : WhiteOnMove(currentMove);
16986     DisplayWhiteClock(whiteTimeRemaining, wom);
16987     DisplayBlackClock(blackTimeRemaining, !wom);
16988 }
16989
16990
16991 /* Timekeeping seems to be a portability nightmare.  I think everyone
16992    has ftime(), but I'm really not sure, so I'm including some ifdefs
16993    to use other calls if you don't.  Clocks will be less accurate if
16994    you have neither ftime nor gettimeofday.
16995 */
16996
16997 /* VS 2008 requires the #include outside of the function */
16998 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16999 #include <sys/timeb.h>
17000 #endif
17001
17002 /* Get the current time as a TimeMark */
17003 void
17004 GetTimeMark (TimeMark *tm)
17005 {
17006 #if HAVE_GETTIMEOFDAY
17007
17008     struct timeval timeVal;
17009     struct timezone timeZone;
17010
17011     gettimeofday(&timeVal, &timeZone);
17012     tm->sec = (long) timeVal.tv_sec;
17013     tm->ms = (int) (timeVal.tv_usec / 1000L);
17014
17015 #else /*!HAVE_GETTIMEOFDAY*/
17016 #if HAVE_FTIME
17017
17018 // include <sys/timeb.h> / moved to just above start of function
17019     struct timeb timeB;
17020
17021     ftime(&timeB);
17022     tm->sec = (long) timeB.time;
17023     tm->ms = (int) timeB.millitm;
17024
17025 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17026     tm->sec = (long) time(NULL);
17027     tm->ms = 0;
17028 #endif
17029 #endif
17030 }
17031
17032 /* Return the difference in milliseconds between two
17033    time marks.  We assume the difference will fit in a long!
17034 */
17035 long
17036 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17037 {
17038     return 1000L*(tm2->sec - tm1->sec) +
17039            (long) (tm2->ms - tm1->ms);
17040 }
17041
17042
17043 /*
17044  * Code to manage the game clocks.
17045  *
17046  * In tournament play, black starts the clock and then white makes a move.
17047  * We give the human user a slight advantage if he is playing white---the
17048  * clocks don't run until he makes his first move, so it takes zero time.
17049  * Also, we don't account for network lag, so we could get out of sync
17050  * with GNU Chess's clock -- but then, referees are always right.
17051  */
17052
17053 static TimeMark tickStartTM;
17054 static long intendedTickLength;
17055
17056 long
17057 NextTickLength (long timeRemaining)
17058 {
17059     long nominalTickLength, nextTickLength;
17060
17061     if (timeRemaining > 0L && timeRemaining <= 10000L)
17062       nominalTickLength = 100L;
17063     else
17064       nominalTickLength = 1000L;
17065     nextTickLength = timeRemaining % nominalTickLength;
17066     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17067
17068     return nextTickLength;
17069 }
17070
17071 /* Adjust clock one minute up or down */
17072 void
17073 AdjustClock (Boolean which, int dir)
17074 {
17075     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17076     if(which) blackTimeRemaining += 60000*dir;
17077     else      whiteTimeRemaining += 60000*dir;
17078     DisplayBothClocks();
17079     adjustedClock = TRUE;
17080 }
17081
17082 /* Stop clocks and reset to a fresh time control */
17083 void
17084 ResetClocks ()
17085 {
17086     (void) StopClockTimer();
17087     if (appData.icsActive) {
17088         whiteTimeRemaining = blackTimeRemaining = 0;
17089     } else if (searchTime) {
17090         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17091         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17092     } else { /* [HGM] correct new time quote for time odds */
17093         whiteTC = blackTC = fullTimeControlString;
17094         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17095         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17096     }
17097     if (whiteFlag || blackFlag) {
17098         DisplayTitle("");
17099         whiteFlag = blackFlag = FALSE;
17100     }
17101     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17102     DisplayBothClocks();
17103     adjustedClock = FALSE;
17104 }
17105
17106 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17107
17108 /* Decrement running clock by amount of time that has passed */
17109 void
17110 DecrementClocks ()
17111 {
17112     long timeRemaining;
17113     long lastTickLength, fudge;
17114     TimeMark now;
17115
17116     if (!appData.clockMode) return;
17117     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17118
17119     GetTimeMark(&now);
17120
17121     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17122
17123     /* Fudge if we woke up a little too soon */
17124     fudge = intendedTickLength - lastTickLength;
17125     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17126
17127     if (WhiteOnMove(forwardMostMove)) {
17128         if(whiteNPS >= 0) lastTickLength = 0;
17129         timeRemaining = whiteTimeRemaining -= lastTickLength;
17130         if(timeRemaining < 0 && !appData.icsActive) {
17131             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17132             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17133                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17134                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17135             }
17136         }
17137         DisplayWhiteClock(whiteTimeRemaining - fudge,
17138                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17139     } else {
17140         if(blackNPS >= 0) lastTickLength = 0;
17141         timeRemaining = blackTimeRemaining -= lastTickLength;
17142         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17143             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17144             if(suddenDeath) {
17145                 blackStartMove = forwardMostMove;
17146                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17147             }
17148         }
17149         DisplayBlackClock(blackTimeRemaining - fudge,
17150                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17151     }
17152     if (CheckFlags()) return;
17153
17154     if(twoBoards) { // count down secondary board's clocks as well
17155         activePartnerTime -= lastTickLength;
17156         partnerUp = 1;
17157         if(activePartner == 'W')
17158             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17159         else
17160             DisplayBlackClock(activePartnerTime, TRUE);
17161         partnerUp = 0;
17162     }
17163
17164     tickStartTM = now;
17165     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17166     StartClockTimer(intendedTickLength);
17167
17168     /* if the time remaining has fallen below the alarm threshold, sound the
17169      * alarm. if the alarm has sounded and (due to a takeback or time control
17170      * with increment) the time remaining has increased to a level above the
17171      * threshold, reset the alarm so it can sound again.
17172      */
17173
17174     if (appData.icsActive && appData.icsAlarm) {
17175
17176         /* make sure we are dealing with the user's clock */
17177         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17178                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17179            )) return;
17180
17181         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17182             alarmSounded = FALSE;
17183         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17184             PlayAlarmSound();
17185             alarmSounded = TRUE;
17186         }
17187     }
17188 }
17189
17190
17191 /* A player has just moved, so stop the previously running
17192    clock and (if in clock mode) start the other one.
17193    We redisplay both clocks in case we're in ICS mode, because
17194    ICS gives us an update to both clocks after every move.
17195    Note that this routine is called *after* forwardMostMove
17196    is updated, so the last fractional tick must be subtracted
17197    from the color that is *not* on move now.
17198 */
17199 void
17200 SwitchClocks (int newMoveNr)
17201 {
17202     long lastTickLength;
17203     TimeMark now;
17204     int flagged = FALSE;
17205
17206     GetTimeMark(&now);
17207
17208     if (StopClockTimer() && appData.clockMode) {
17209         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17210         if (!WhiteOnMove(forwardMostMove)) {
17211             if(blackNPS >= 0) lastTickLength = 0;
17212             blackTimeRemaining -= lastTickLength;
17213            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17214 //         if(pvInfoList[forwardMostMove].time == -1)
17215                  pvInfoList[forwardMostMove].time =               // use GUI time
17216                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17217         } else {
17218            if(whiteNPS >= 0) lastTickLength = 0;
17219            whiteTimeRemaining -= lastTickLength;
17220            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17221 //         if(pvInfoList[forwardMostMove].time == -1)
17222                  pvInfoList[forwardMostMove].time =
17223                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17224         }
17225         flagged = CheckFlags();
17226     }
17227     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17228     CheckTimeControl();
17229
17230     if (flagged || !appData.clockMode) return;
17231
17232     switch (gameMode) {
17233       case MachinePlaysBlack:
17234       case MachinePlaysWhite:
17235       case BeginningOfGame:
17236         if (pausing) return;
17237         break;
17238
17239       case EditGame:
17240       case PlayFromGameFile:
17241       case IcsExamining:
17242         return;
17243
17244       default:
17245         break;
17246     }
17247
17248     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17249         if(WhiteOnMove(forwardMostMove))
17250              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17251         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17252     }
17253
17254     tickStartTM = now;
17255     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17256       whiteTimeRemaining : blackTimeRemaining);
17257     StartClockTimer(intendedTickLength);
17258 }
17259
17260
17261 /* Stop both clocks */
17262 void
17263 StopClocks ()
17264 {
17265     long lastTickLength;
17266     TimeMark now;
17267
17268     if (!StopClockTimer()) return;
17269     if (!appData.clockMode) return;
17270
17271     GetTimeMark(&now);
17272
17273     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17274     if (WhiteOnMove(forwardMostMove)) {
17275         if(whiteNPS >= 0) lastTickLength = 0;
17276         whiteTimeRemaining -= lastTickLength;
17277         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17278     } else {
17279         if(blackNPS >= 0) lastTickLength = 0;
17280         blackTimeRemaining -= lastTickLength;
17281         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17282     }
17283     CheckFlags();
17284 }
17285
17286 /* Start clock of player on move.  Time may have been reset, so
17287    if clock is already running, stop and restart it. */
17288 void
17289 StartClocks ()
17290 {
17291     (void) StopClockTimer(); /* in case it was running already */
17292     DisplayBothClocks();
17293     if (CheckFlags()) return;
17294
17295     if (!appData.clockMode) return;
17296     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17297
17298     GetTimeMark(&tickStartTM);
17299     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17300       whiteTimeRemaining : blackTimeRemaining);
17301
17302    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17303     whiteNPS = blackNPS = -1;
17304     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17305        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17306         whiteNPS = first.nps;
17307     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17308        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17309         blackNPS = first.nps;
17310     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17311         whiteNPS = second.nps;
17312     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17313         blackNPS = second.nps;
17314     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17315
17316     StartClockTimer(intendedTickLength);
17317 }
17318
17319 char *
17320 TimeString (long ms)
17321 {
17322     long second, minute, hour, day;
17323     char *sign = "";
17324     static char buf[32];
17325
17326     if (ms > 0 && ms <= 9900) {
17327       /* convert milliseconds to tenths, rounding up */
17328       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17329
17330       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17331       return buf;
17332     }
17333
17334     /* convert milliseconds to seconds, rounding up */
17335     /* use floating point to avoid strangeness of integer division
17336        with negative dividends on many machines */
17337     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17338
17339     if (second < 0) {
17340         sign = "-";
17341         second = -second;
17342     }
17343
17344     day = second / (60 * 60 * 24);
17345     second = second % (60 * 60 * 24);
17346     hour = second / (60 * 60);
17347     second = second % (60 * 60);
17348     minute = second / 60;
17349     second = second % 60;
17350
17351     if (day > 0)
17352       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17353               sign, day, hour, minute, second);
17354     else if (hour > 0)
17355       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17356     else
17357       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17358
17359     return buf;
17360 }
17361
17362
17363 /*
17364  * This is necessary because some C libraries aren't ANSI C compliant yet.
17365  */
17366 char *
17367 StrStr (char *string, char *match)
17368 {
17369     int i, length;
17370
17371     length = strlen(match);
17372
17373     for (i = strlen(string) - length; i >= 0; i--, string++)
17374       if (!strncmp(match, string, length))
17375         return string;
17376
17377     return NULL;
17378 }
17379
17380 char *
17381 StrCaseStr (char *string, char *match)
17382 {
17383     int i, j, length;
17384
17385     length = strlen(match);
17386
17387     for (i = strlen(string) - length; i >= 0; i--, string++) {
17388         for (j = 0; j < length; j++) {
17389             if (ToLower(match[j]) != ToLower(string[j]))
17390               break;
17391         }
17392         if (j == length) return string;
17393     }
17394
17395     return NULL;
17396 }
17397
17398 #ifndef _amigados
17399 int
17400 StrCaseCmp (char *s1, char *s2)
17401 {
17402     char c1, c2;
17403
17404     for (;;) {
17405         c1 = ToLower(*s1++);
17406         c2 = ToLower(*s2++);
17407         if (c1 > c2) return 1;
17408         if (c1 < c2) return -1;
17409         if (c1 == NULLCHAR) return 0;
17410     }
17411 }
17412
17413
17414 int
17415 ToLower (int c)
17416 {
17417     return isupper(c) ? tolower(c) : c;
17418 }
17419
17420
17421 int
17422 ToUpper (int c)
17423 {
17424     return islower(c) ? toupper(c) : c;
17425 }
17426 #endif /* !_amigados    */
17427
17428 char *
17429 StrSave (char *s)
17430 {
17431   char *ret;
17432
17433   if ((ret = (char *) malloc(strlen(s) + 1)))
17434     {
17435       safeStrCpy(ret, s, strlen(s)+1);
17436     }
17437   return ret;
17438 }
17439
17440 char *
17441 StrSavePtr (char *s, char **savePtr)
17442 {
17443     if (*savePtr) {
17444         free(*savePtr);
17445     }
17446     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17447       safeStrCpy(*savePtr, s, strlen(s)+1);
17448     }
17449     return(*savePtr);
17450 }
17451
17452 char *
17453 PGNDate ()
17454 {
17455     time_t clock;
17456     struct tm *tm;
17457     char buf[MSG_SIZ];
17458
17459     clock = time((time_t *)NULL);
17460     tm = localtime(&clock);
17461     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17462             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17463     return StrSave(buf);
17464 }
17465
17466
17467 char *
17468 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17469 {
17470     int i, j, fromX, fromY, toX, toY;
17471     int whiteToPlay;
17472     char buf[MSG_SIZ];
17473     char *p, *q;
17474     int emptycount;
17475     ChessSquare piece;
17476
17477     whiteToPlay = (gameMode == EditPosition) ?
17478       !blackPlaysFirst : (move % 2 == 0);
17479     p = buf;
17480
17481     /* Piece placement data */
17482     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17483         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17484         emptycount = 0;
17485         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17486             if (boards[move][i][j] == EmptySquare) {
17487                 emptycount++;
17488             } else { ChessSquare piece = boards[move][i][j];
17489                 if (emptycount > 0) {
17490                     if(emptycount<10) /* [HGM] can be >= 10 */
17491                         *p++ = '0' + emptycount;
17492                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17493                     emptycount = 0;
17494                 }
17495                 if(PieceToChar(piece) == '+') {
17496                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17497                     *p++ = '+';
17498                     piece = (ChessSquare)(DEMOTED piece);
17499                 }
17500                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17501                 if(p[-1] == '~') {
17502                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17503                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17504                     *p++ = '~';
17505                 }
17506             }
17507         }
17508         if (emptycount > 0) {
17509             if(emptycount<10) /* [HGM] can be >= 10 */
17510                 *p++ = '0' + emptycount;
17511             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17512             emptycount = 0;
17513         }
17514         *p++ = '/';
17515     }
17516     *(p - 1) = ' ';
17517
17518     /* [HGM] print Crazyhouse or Shogi holdings */
17519     if( gameInfo.holdingsWidth ) {
17520         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17521         q = p;
17522         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17523             piece = boards[move][i][BOARD_WIDTH-1];
17524             if( piece != EmptySquare )
17525               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17526                   *p++ = PieceToChar(piece);
17527         }
17528         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17529             piece = boards[move][BOARD_HEIGHT-i-1][0];
17530             if( piece != EmptySquare )
17531               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17532                   *p++ = PieceToChar(piece);
17533         }
17534
17535         if( q == p ) *p++ = '-';
17536         *p++ = ']';
17537         *p++ = ' ';
17538     }
17539
17540     /* Active color */
17541     *p++ = whiteToPlay ? 'w' : 'b';
17542     *p++ = ' ';
17543
17544   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17545     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17546   } else {
17547   if(nrCastlingRights) {
17548      q = p;
17549      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17550        /* [HGM] write directly from rights */
17551            if(boards[move][CASTLING][2] != NoRights &&
17552               boards[move][CASTLING][0] != NoRights   )
17553                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17554            if(boards[move][CASTLING][2] != NoRights &&
17555               boards[move][CASTLING][1] != NoRights   )
17556                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17557            if(boards[move][CASTLING][5] != NoRights &&
17558               boards[move][CASTLING][3] != NoRights   )
17559                 *p++ = boards[move][CASTLING][3] + AAA;
17560            if(boards[move][CASTLING][5] != NoRights &&
17561               boards[move][CASTLING][4] != NoRights   )
17562                 *p++ = boards[move][CASTLING][4] + AAA;
17563      } else {
17564
17565         /* [HGM] write true castling rights */
17566         if( nrCastlingRights == 6 ) {
17567             int q, k=0;
17568             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17569                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17570             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17571                  boards[move][CASTLING][2] != NoRights  );
17572             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17573                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17574                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17575                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17576                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17577             }
17578             if(q) *p++ = 'Q';
17579             k = 0;
17580             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17581                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17582             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17583                  boards[move][CASTLING][5] != NoRights  );
17584             if(gameInfo.variant == VariantSChess) {
17585                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17586                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17587                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17588                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17589             }
17590             if(q) *p++ = 'q';
17591         }
17592      }
17593      if (q == p) *p++ = '-'; /* No castling rights */
17594      *p++ = ' ';
17595   }
17596
17597   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17598      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17599      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17600     /* En passant target square */
17601     if (move > backwardMostMove) {
17602         fromX = moveList[move - 1][0] - AAA;
17603         fromY = moveList[move - 1][1] - ONE;
17604         toX = moveList[move - 1][2] - AAA;
17605         toY = moveList[move - 1][3] - ONE;
17606         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17607             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17608             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17609             fromX == toX) {
17610             /* 2-square pawn move just happened */
17611             *p++ = toX + AAA;
17612             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17613         } else {
17614             *p++ = '-';
17615         }
17616     } else if(move == backwardMostMove) {
17617         // [HGM] perhaps we should always do it like this, and forget the above?
17618         if((signed char)boards[move][EP_STATUS] >= 0) {
17619             *p++ = boards[move][EP_STATUS] + AAA;
17620             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17621         } else {
17622             *p++ = '-';
17623         }
17624     } else {
17625         *p++ = '-';
17626     }
17627     *p++ = ' ';
17628   }
17629   }
17630
17631     if(moveCounts)
17632     {   int i = 0, j=move;
17633
17634         /* [HGM] find reversible plies */
17635         if (appData.debugMode) { int k;
17636             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17637             for(k=backwardMostMove; k<=forwardMostMove; k++)
17638                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17639
17640         }
17641
17642         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17643         if( j == backwardMostMove ) i += initialRulePlies;
17644         sprintf(p, "%d ", i);
17645         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17646
17647         /* Fullmove number */
17648         sprintf(p, "%d", (move / 2) + 1);
17649     } else *--p = NULLCHAR;
17650
17651     return StrSave(buf);
17652 }
17653
17654 Boolean
17655 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17656 {
17657     int i, j, k, w=0;
17658     char *p, c;
17659     int emptycount, virgin[BOARD_FILES];
17660     ChessSquare piece;
17661
17662     p = fen;
17663
17664     /* Piece placement data */
17665     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17666         j = 0;
17667         for (;;) {
17668             if (*p == '/' || *p == ' ' || *p == '[' ) {
17669                 if(j > w) w = j;
17670                 emptycount = gameInfo.boardWidth - j;
17671                 while (emptycount--)
17672                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17673                 if (*p == '/') p++;
17674                 else if(autoSize) { // we stumbled unexpectedly into end of board
17675                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17676                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17677                     }
17678                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17679                 }
17680                 break;
17681 #if(BOARD_FILES >= 10)
17682             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17683                 p++; emptycount=10;
17684                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17685                 while (emptycount--)
17686                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17687 #endif
17688             } else if (*p == '*') {
17689                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17690             } else if (isdigit(*p)) {
17691                 emptycount = *p++ - '0';
17692                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17693                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17694                 while (emptycount--)
17695                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17696             } else if (*p == '+' || isalpha(*p)) {
17697                 if (j >= gameInfo.boardWidth) return FALSE;
17698                 if(*p=='+') {
17699                     piece = CharToPiece(*++p);
17700                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17701                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17702                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17703                 } else piece = CharToPiece(*p++);
17704
17705                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17706                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17707                     piece = (ChessSquare) (PROMOTED piece);
17708                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17709                     p++;
17710                 }
17711                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17712             } else {
17713                 return FALSE;
17714             }
17715         }
17716     }
17717     while (*p == '/' || *p == ' ') p++;
17718
17719     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17720
17721     /* [HGM] by default clear Crazyhouse holdings, if present */
17722     if(gameInfo.holdingsWidth) {
17723        for(i=0; i<BOARD_HEIGHT; i++) {
17724            board[i][0]             = EmptySquare; /* black holdings */
17725            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17726            board[i][1]             = (ChessSquare) 0; /* black counts */
17727            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17728        }
17729     }
17730
17731     /* [HGM] look for Crazyhouse holdings here */
17732     while(*p==' ') p++;
17733     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17734         if(*p == '[') p++;
17735         if(*p == '-' ) p++; /* empty holdings */ else {
17736             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17737             /* if we would allow FEN reading to set board size, we would   */
17738             /* have to add holdings and shift the board read so far here   */
17739             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17740                 p++;
17741                 if((int) piece >= (int) BlackPawn ) {
17742                     i = (int)piece - (int)BlackPawn;
17743                     i = PieceToNumber((ChessSquare)i);
17744                     if( i >= gameInfo.holdingsSize ) return FALSE;
17745                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17746                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17747                 } else {
17748                     i = (int)piece - (int)WhitePawn;
17749                     i = PieceToNumber((ChessSquare)i);
17750                     if( i >= gameInfo.holdingsSize ) return FALSE;
17751                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17752                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17753                 }
17754             }
17755         }
17756         if(*p == ']') p++;
17757     }
17758
17759     while(*p == ' ') p++;
17760
17761     /* Active color */
17762     c = *p++;
17763     if(appData.colorNickNames) {
17764       if( c == appData.colorNickNames[0] ) c = 'w'; else
17765       if( c == appData.colorNickNames[1] ) c = 'b';
17766     }
17767     switch (c) {
17768       case 'w':
17769         *blackPlaysFirst = FALSE;
17770         break;
17771       case 'b':
17772         *blackPlaysFirst = TRUE;
17773         break;
17774       default:
17775         return FALSE;
17776     }
17777
17778     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17779     /* return the extra info in global variiables             */
17780
17781     /* set defaults in case FEN is incomplete */
17782     board[EP_STATUS] = EP_UNKNOWN;
17783     for(i=0; i<nrCastlingRights; i++ ) {
17784         board[CASTLING][i] =
17785             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17786     }   /* assume possible unless obviously impossible */
17787     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17788     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17789     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17790                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17791     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17792     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17793     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17794                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17795     FENrulePlies = 0;
17796
17797     while(*p==' ') p++;
17798     if(nrCastlingRights) {
17799       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17800       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17801           /* castling indicator present, so default becomes no castlings */
17802           for(i=0; i<nrCastlingRights; i++ ) {
17803                  board[CASTLING][i] = NoRights;
17804           }
17805       }
17806       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17807              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17808              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17809              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17810         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17811
17812         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17813             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17814             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17815         }
17816         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17817             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17818         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17819                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17820         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17821                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17822         switch(c) {
17823           case'K':
17824               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17825               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17826               board[CASTLING][2] = whiteKingFile;
17827               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17828               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17829               break;
17830           case'Q':
17831               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17832               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17833               board[CASTLING][2] = whiteKingFile;
17834               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17835               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17836               break;
17837           case'k':
17838               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17839               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17840               board[CASTLING][5] = blackKingFile;
17841               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17842               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17843               break;
17844           case'q':
17845               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17846               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17847               board[CASTLING][5] = blackKingFile;
17848               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17849               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17850           case '-':
17851               break;
17852           default: /* FRC castlings */
17853               if(c >= 'a') { /* black rights */
17854                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17855                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17856                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17857                   if(i == BOARD_RGHT) break;
17858                   board[CASTLING][5] = i;
17859                   c -= AAA;
17860                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17861                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17862                   if(c > i)
17863                       board[CASTLING][3] = c;
17864                   else
17865                       board[CASTLING][4] = c;
17866               } else { /* white rights */
17867                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17868                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17869                     if(board[0][i] == WhiteKing) break;
17870                   if(i == BOARD_RGHT) break;
17871                   board[CASTLING][2] = i;
17872                   c -= AAA - 'a' + 'A';
17873                   if(board[0][c] >= WhiteKing) break;
17874                   if(c > i)
17875                       board[CASTLING][0] = c;
17876                   else
17877                       board[CASTLING][1] = c;
17878               }
17879         }
17880       }
17881       for(i=0; i<nrCastlingRights; i++)
17882         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17883       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17884     if (appData.debugMode) {
17885         fprintf(debugFP, "FEN castling rights:");
17886         for(i=0; i<nrCastlingRights; i++)
17887         fprintf(debugFP, " %d", board[CASTLING][i]);
17888         fprintf(debugFP, "\n");
17889     }
17890
17891       while(*p==' ') p++;
17892     }
17893
17894     /* read e.p. field in games that know e.p. capture */
17895     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17896        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17897        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17898       if(*p=='-') {
17899         p++; board[EP_STATUS] = EP_NONE;
17900       } else {
17901          char c = *p++ - AAA;
17902
17903          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17904          if(*p >= '0' && *p <='9') p++;
17905          board[EP_STATUS] = c;
17906       }
17907     }
17908
17909
17910     if(sscanf(p, "%d", &i) == 1) {
17911         FENrulePlies = i; /* 50-move ply counter */
17912         /* (The move number is still ignored)    */
17913     }
17914
17915     return TRUE;
17916 }
17917
17918 void
17919 EditPositionPasteFEN (char *fen)
17920 {
17921   if (fen != NULL) {
17922     Board initial_position;
17923
17924     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17925       DisplayError(_("Bad FEN position in clipboard"), 0);
17926       return ;
17927     } else {
17928       int savedBlackPlaysFirst = blackPlaysFirst;
17929       EditPositionEvent();
17930       blackPlaysFirst = savedBlackPlaysFirst;
17931       CopyBoard(boards[0], initial_position);
17932       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17933       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17934       DisplayBothClocks();
17935       DrawPosition(FALSE, boards[currentMove]);
17936     }
17937   }
17938 }
17939
17940 static char cseq[12] = "\\   ";
17941
17942 Boolean
17943 set_cont_sequence (char *new_seq)
17944 {
17945     int len;
17946     Boolean ret;
17947
17948     // handle bad attempts to set the sequence
17949         if (!new_seq)
17950                 return 0; // acceptable error - no debug
17951
17952     len = strlen(new_seq);
17953     ret = (len > 0) && (len < sizeof(cseq));
17954     if (ret)
17955       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17956     else if (appData.debugMode)
17957       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17958     return ret;
17959 }
17960
17961 /*
17962     reformat a source message so words don't cross the width boundary.  internal
17963     newlines are not removed.  returns the wrapped size (no null character unless
17964     included in source message).  If dest is NULL, only calculate the size required
17965     for the dest buffer.  lp argument indicats line position upon entry, and it's
17966     passed back upon exit.
17967 */
17968 int
17969 wrap (char *dest, char *src, int count, int width, int *lp)
17970 {
17971     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17972
17973     cseq_len = strlen(cseq);
17974     old_line = line = *lp;
17975     ansi = len = clen = 0;
17976
17977     for (i=0; i < count; i++)
17978     {
17979         if (src[i] == '\033')
17980             ansi = 1;
17981
17982         // if we hit the width, back up
17983         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17984         {
17985             // store i & len in case the word is too long
17986             old_i = i, old_len = len;
17987
17988             // find the end of the last word
17989             while (i && src[i] != ' ' && src[i] != '\n')
17990             {
17991                 i--;
17992                 len--;
17993             }
17994
17995             // word too long?  restore i & len before splitting it
17996             if ((old_i-i+clen) >= width)
17997             {
17998                 i = old_i;
17999                 len = old_len;
18000             }
18001
18002             // extra space?
18003             if (i && src[i-1] == ' ')
18004                 len--;
18005
18006             if (src[i] != ' ' && src[i] != '\n')
18007             {
18008                 i--;
18009                 if (len)
18010                     len--;
18011             }
18012
18013             // now append the newline and continuation sequence
18014             if (dest)
18015                 dest[len] = '\n';
18016             len++;
18017             if (dest)
18018                 strncpy(dest+len, cseq, cseq_len);
18019             len += cseq_len;
18020             line = cseq_len;
18021             clen = cseq_len;
18022             continue;
18023         }
18024
18025         if (dest)
18026             dest[len] = src[i];
18027         len++;
18028         if (!ansi)
18029             line++;
18030         if (src[i] == '\n')
18031             line = 0;
18032         if (src[i] == 'm')
18033             ansi = 0;
18034     }
18035     if (dest && appData.debugMode)
18036     {
18037         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18038             count, width, line, len, *lp);
18039         show_bytes(debugFP, src, count);
18040         fprintf(debugFP, "\ndest: ");
18041         show_bytes(debugFP, dest, len);
18042         fprintf(debugFP, "\n");
18043     }
18044     *lp = dest ? line : old_line;
18045
18046     return len;
18047 }
18048
18049 // [HGM] vari: routines for shelving variations
18050 Boolean modeRestore = FALSE;
18051
18052 void
18053 PushInner (int firstMove, int lastMove)
18054 {
18055         int i, j, nrMoves = lastMove - firstMove;
18056
18057         // push current tail of game on stack
18058         savedResult[storedGames] = gameInfo.result;
18059         savedDetails[storedGames] = gameInfo.resultDetails;
18060         gameInfo.resultDetails = NULL;
18061         savedFirst[storedGames] = firstMove;
18062         savedLast [storedGames] = lastMove;
18063         savedFramePtr[storedGames] = framePtr;
18064         framePtr -= nrMoves; // reserve space for the boards
18065         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18066             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18067             for(j=0; j<MOVE_LEN; j++)
18068                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18069             for(j=0; j<2*MOVE_LEN; j++)
18070                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18071             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18072             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18073             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18074             pvInfoList[firstMove+i-1].depth = 0;
18075             commentList[framePtr+i] = commentList[firstMove+i];
18076             commentList[firstMove+i] = NULL;
18077         }
18078
18079         storedGames++;
18080         forwardMostMove = firstMove; // truncate game so we can start variation
18081 }
18082
18083 void
18084 PushTail (int firstMove, int lastMove)
18085 {
18086         if(appData.icsActive) { // only in local mode
18087                 forwardMostMove = currentMove; // mimic old ICS behavior
18088                 return;
18089         }
18090         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18091
18092         PushInner(firstMove, lastMove);
18093         if(storedGames == 1) GreyRevert(FALSE);
18094         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18095 }
18096
18097 void
18098 PopInner (Boolean annotate)
18099 {
18100         int i, j, nrMoves;
18101         char buf[8000], moveBuf[20];
18102
18103         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18104         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18105         nrMoves = savedLast[storedGames] - currentMove;
18106         if(annotate) {
18107                 int cnt = 10;
18108                 if(!WhiteOnMove(currentMove))
18109                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18110                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18111                 for(i=currentMove; i<forwardMostMove; i++) {
18112                         if(WhiteOnMove(i))
18113                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18114                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18115                         strcat(buf, moveBuf);
18116                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18117                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18118                 }
18119                 strcat(buf, ")");
18120         }
18121         for(i=1; i<=nrMoves; i++) { // copy last variation back
18122             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18123             for(j=0; j<MOVE_LEN; j++)
18124                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18125             for(j=0; j<2*MOVE_LEN; j++)
18126                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18127             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18128             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18129             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18130             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18131             commentList[currentMove+i] = commentList[framePtr+i];
18132             commentList[framePtr+i] = NULL;
18133         }
18134         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18135         framePtr = savedFramePtr[storedGames];
18136         gameInfo.result = savedResult[storedGames];
18137         if(gameInfo.resultDetails != NULL) {
18138             free(gameInfo.resultDetails);
18139       }
18140         gameInfo.resultDetails = savedDetails[storedGames];
18141         forwardMostMove = currentMove + nrMoves;
18142 }
18143
18144 Boolean
18145 PopTail (Boolean annotate)
18146 {
18147         if(appData.icsActive) return FALSE; // only in local mode
18148         if(!storedGames) return FALSE; // sanity
18149         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18150
18151         PopInner(annotate);
18152         if(currentMove < forwardMostMove) ForwardEvent(); else
18153         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18154
18155         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18156         return TRUE;
18157 }
18158
18159 void
18160 CleanupTail ()
18161 {       // remove all shelved variations
18162         int i;
18163         for(i=0; i<storedGames; i++) {
18164             if(savedDetails[i])
18165                 free(savedDetails[i]);
18166             savedDetails[i] = NULL;
18167         }
18168         for(i=framePtr; i<MAX_MOVES; i++) {
18169                 if(commentList[i]) free(commentList[i]);
18170                 commentList[i] = NULL;
18171         }
18172         framePtr = MAX_MOVES-1;
18173         storedGames = 0;
18174 }
18175
18176 void
18177 LoadVariation (int index, char *text)
18178 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18179         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18180         int level = 0, move;
18181
18182         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18183         // first find outermost bracketing variation
18184         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18185             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18186                 if(*p == '{') wait = '}'; else
18187                 if(*p == '[') wait = ']'; else
18188                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18189                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18190             }
18191             if(*p == wait) wait = NULLCHAR; // closing ]} found
18192             p++;
18193         }
18194         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18195         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18196         end[1] = NULLCHAR; // clip off comment beyond variation
18197         ToNrEvent(currentMove-1);
18198         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18199         // kludge: use ParsePV() to append variation to game
18200         move = currentMove;
18201         ParsePV(start, TRUE, TRUE);
18202         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18203         ClearPremoveHighlights();
18204         CommentPopDown();
18205         ToNrEvent(currentMove+1);
18206 }
18207
18208 void
18209 LoadTheme ()
18210 {
18211     char *p, *q, buf[MSG_SIZ];
18212     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18213         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18214         ParseArgsFromString(buf);
18215         ActivateTheme(TRUE); // also redo colors
18216         return;
18217     }
18218     p = nickName;
18219     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18220     {
18221         int len;
18222         q = appData.themeNames;
18223         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18224       if(appData.useBitmaps) {
18225         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18226                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18227                 appData.liteBackTextureMode,
18228                 appData.darkBackTextureMode );
18229       } else {
18230         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18231                 Col2Text(2),   // lightSquareColor
18232                 Col2Text(3) ); // darkSquareColor
18233       }
18234       if(appData.useBorder) {
18235         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18236                 appData.border);
18237       } else {
18238         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18239       }
18240       if(appData.useFont) {
18241         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18242                 appData.renderPiecesWithFont,
18243                 appData.fontToPieceTable,
18244                 Col2Text(9),    // appData.fontBackColorWhite
18245                 Col2Text(10) ); // appData.fontForeColorBlack
18246       } else {
18247         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18248                 appData.pieceDirectory);
18249         if(!appData.pieceDirectory[0])
18250           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18251                 Col2Text(0),   // whitePieceColor
18252                 Col2Text(1) ); // blackPieceColor
18253       }
18254       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18255                 Col2Text(4),   // highlightSquareColor
18256                 Col2Text(5) ); // premoveHighlightColor
18257         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18258         if(insert != q) insert[-1] = NULLCHAR;
18259         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18260         if(q)   free(q);
18261     }
18262     ActivateTheme(FALSE);
18263 }