Fix default of Chu Chess piece promotions
[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     InitChessProgram(&first, startedFromSetupPosition);
1568
1569     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1570         free(programVersion);
1571         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1572         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1573         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1574     }
1575
1576     if (appData.icsActive) {
1577 #ifdef WIN32
1578         /* [DM] Make a console window if needed [HGM] merged ifs */
1579         ConsoleCreate();
1580 #endif
1581         err = establish();
1582         if (err != 0)
1583           {
1584             if (*appData.icsCommPort != NULLCHAR)
1585               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1586                              appData.icsCommPort);
1587             else
1588               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1589                         appData.icsHost, appData.icsPort);
1590
1591             if( (len >= MSG_SIZ) && appData.debugMode )
1592               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1593
1594             DisplayFatalError(buf, err, 1);
1595             return;
1596         }
1597         SetICSMode();
1598         telnetISR =
1599           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1600         fromUserISR =
1601           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1602         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1603             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1604     } else if (appData.noChessProgram) {
1605         SetNCPMode();
1606     } else {
1607         SetGNUMode();
1608     }
1609
1610     if (*appData.cmailGameName != NULLCHAR) {
1611         SetCmailMode();
1612         OpenLoopback(&cmailPR);
1613         cmailISR =
1614           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1615     }
1616
1617     ThawUI();
1618     DisplayMessage("", "");
1619     if (StrCaseCmp(appData.initialMode, "") == 0) {
1620       initialMode = BeginningOfGame;
1621       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1622         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1623         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1624         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1625         ModeHighlight();
1626       }
1627     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1628       initialMode = TwoMachinesPlay;
1629     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1630       initialMode = AnalyzeFile;
1631     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1632       initialMode = AnalyzeMode;
1633     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1634       initialMode = MachinePlaysWhite;
1635     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1636       initialMode = MachinePlaysBlack;
1637     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1638       initialMode = EditGame;
1639     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1640       initialMode = EditPosition;
1641     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1642       initialMode = Training;
1643     } else {
1644       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1645       if( (len >= MSG_SIZ) && appData.debugMode )
1646         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648       DisplayFatalError(buf, 0, 2);
1649       return;
1650     }
1651
1652     if (appData.matchMode) {
1653         if(appData.tourneyFile[0]) { // start tourney from command line
1654             FILE *f;
1655             if(f = fopen(appData.tourneyFile, "r")) {
1656                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1657                 fclose(f);
1658                 appData.clockMode = TRUE;
1659                 SetGNUMode();
1660             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1661         }
1662         MatchEvent(TRUE);
1663     } else if (*appData.cmailGameName != NULLCHAR) {
1664         /* Set up cmail mode */
1665         ReloadCmailMsgEvent(TRUE);
1666     } else {
1667         /* Set up other modes */
1668         if (initialMode == AnalyzeFile) {
1669           if (*appData.loadGameFile == NULLCHAR) {
1670             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1671             return;
1672           }
1673         }
1674         if (*appData.loadGameFile != NULLCHAR) {
1675             (void) LoadGameFromFile(appData.loadGameFile,
1676                                     appData.loadGameIndex,
1677                                     appData.loadGameFile, TRUE);
1678         } else if (*appData.loadPositionFile != NULLCHAR) {
1679             (void) LoadPositionFromFile(appData.loadPositionFile,
1680                                         appData.loadPositionIndex,
1681                                         appData.loadPositionFile);
1682             /* [HGM] try to make self-starting even after FEN load */
1683             /* to allow automatic setup of fairy variants with wtm */
1684             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1685                 gameMode = BeginningOfGame;
1686                 setboardSpoiledMachineBlack = 1;
1687             }
1688             /* [HGM] loadPos: make that every new game uses the setup */
1689             /* from file as long as we do not switch variant          */
1690             if(!blackPlaysFirst) {
1691                 startedFromPositionFile = TRUE;
1692                 CopyBoard(filePosition, boards[0]);
1693             }
1694         }
1695         if (initialMode == AnalyzeMode) {
1696           if (appData.noChessProgram) {
1697             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1698             return;
1699           }
1700           if (appData.icsActive) {
1701             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1702             return;
1703           }
1704           AnalyzeModeEvent();
1705         } else if (initialMode == AnalyzeFile) {
1706           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1707           ShowThinkingEvent();
1708           AnalyzeFileEvent();
1709           AnalysisPeriodicEvent(1);
1710         } else if (initialMode == MachinePlaysWhite) {
1711           if (appData.noChessProgram) {
1712             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1713                               0, 2);
1714             return;
1715           }
1716           if (appData.icsActive) {
1717             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1718                               0, 2);
1719             return;
1720           }
1721           MachineWhiteEvent();
1722         } else if (initialMode == MachinePlaysBlack) {
1723           if (appData.noChessProgram) {
1724             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1725                               0, 2);
1726             return;
1727           }
1728           if (appData.icsActive) {
1729             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1730                               0, 2);
1731             return;
1732           }
1733           MachineBlackEvent();
1734         } else if (initialMode == TwoMachinesPlay) {
1735           if (appData.noChessProgram) {
1736             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1737                               0, 2);
1738             return;
1739           }
1740           if (appData.icsActive) {
1741             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1742                               0, 2);
1743             return;
1744           }
1745           TwoMachinesEvent();
1746         } else if (initialMode == EditGame) {
1747           EditGameEvent();
1748         } else if (initialMode == EditPosition) {
1749           EditPositionEvent();
1750         } else if (initialMode == Training) {
1751           if (*appData.loadGameFile == NULLCHAR) {
1752             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1753             return;
1754           }
1755           TrainingEvent();
1756         }
1757     }
1758 }
1759
1760 void
1761 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1762 {
1763     DisplayBook(current+1);
1764
1765     MoveHistorySet( movelist, first, last, current, pvInfoList );
1766
1767     EvalGraphSet( first, last, current, pvInfoList );
1768
1769     MakeEngineOutputTitle();
1770 }
1771
1772 /*
1773  * Establish will establish a contact to a remote host.port.
1774  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1775  *  used to talk to the host.
1776  * Returns 0 if okay, error code if not.
1777  */
1778 int
1779 establish ()
1780 {
1781     char buf[MSG_SIZ];
1782
1783     if (*appData.icsCommPort != NULLCHAR) {
1784         /* Talk to the host through a serial comm port */
1785         return OpenCommPort(appData.icsCommPort, &icsPR);
1786
1787     } else if (*appData.gateway != NULLCHAR) {
1788         if (*appData.remoteShell == NULLCHAR) {
1789             /* Use the rcmd protocol to run telnet program on a gateway host */
1790             snprintf(buf, sizeof(buf), "%s %s %s",
1791                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1792             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1793
1794         } else {
1795             /* Use the rsh program to run telnet program on a gateway host */
1796             if (*appData.remoteUser == NULLCHAR) {
1797                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1798                         appData.gateway, appData.telnetProgram,
1799                         appData.icsHost, appData.icsPort);
1800             } else {
1801                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1802                         appData.remoteShell, appData.gateway,
1803                         appData.remoteUser, appData.telnetProgram,
1804                         appData.icsHost, appData.icsPort);
1805             }
1806             return StartChildProcess(buf, "", &icsPR);
1807
1808         }
1809     } else if (appData.useTelnet) {
1810         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1811
1812     } else {
1813         /* TCP socket interface differs somewhat between
1814            Unix and NT; handle details in the front end.
1815            */
1816         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1817     }
1818 }
1819
1820 void
1821 EscapeExpand (char *p, char *q)
1822 {       // [HGM] initstring: routine to shape up string arguments
1823         while(*p++ = *q++) if(p[-1] == '\\')
1824             switch(*q++) {
1825                 case 'n': p[-1] = '\n'; break;
1826                 case 'r': p[-1] = '\r'; break;
1827                 case 't': p[-1] = '\t'; break;
1828                 case '\\': p[-1] = '\\'; break;
1829                 case 0: *p = 0; return;
1830                 default: p[-1] = q[-1]; break;
1831             }
1832 }
1833
1834 void
1835 show_bytes (FILE *fp, char *buf, int count)
1836 {
1837     while (count--) {
1838         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1839             fprintf(fp, "\\%03o", *buf & 0xff);
1840         } else {
1841             putc(*buf, fp);
1842         }
1843         buf++;
1844     }
1845     fflush(fp);
1846 }
1847
1848 /* Returns an errno value */
1849 int
1850 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1851 {
1852     char buf[8192], *p, *q, *buflim;
1853     int left, newcount, outcount;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1856         *appData.gateway != NULLCHAR) {
1857         if (appData.debugMode) {
1858             fprintf(debugFP, ">ICS: ");
1859             show_bytes(debugFP, message, count);
1860             fprintf(debugFP, "\n");
1861         }
1862         return OutputToProcess(pr, message, count, outError);
1863     }
1864
1865     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1866     p = message;
1867     q = buf;
1868     left = count;
1869     newcount = 0;
1870     while (left) {
1871         if (q >= buflim) {
1872             if (appData.debugMode) {
1873                 fprintf(debugFP, ">ICS: ");
1874                 show_bytes(debugFP, buf, newcount);
1875                 fprintf(debugFP, "\n");
1876             }
1877             outcount = OutputToProcess(pr, buf, newcount, outError);
1878             if (outcount < newcount) return -1; /* to be sure */
1879             q = buf;
1880             newcount = 0;
1881         }
1882         if (*p == '\n') {
1883             *q++ = '\r';
1884             newcount++;
1885         } else if (((unsigned char) *p) == TN_IAC) {
1886             *q++ = (char) TN_IAC;
1887             newcount ++;
1888         }
1889         *q++ = *p++;
1890         newcount++;
1891         left--;
1892     }
1893     if (appData.debugMode) {
1894         fprintf(debugFP, ">ICS: ");
1895         show_bytes(debugFP, buf, newcount);
1896         fprintf(debugFP, "\n");
1897     }
1898     outcount = OutputToProcess(pr, buf, newcount, outError);
1899     if (outcount < newcount) return -1; /* to be sure */
1900     return count;
1901 }
1902
1903 void
1904 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1905 {
1906     int outError, outCount;
1907     static int gotEof = 0;
1908     static FILE *ini;
1909
1910     /* Pass data read from player on to ICS */
1911     if (count > 0) {
1912         gotEof = 0;
1913         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1914         if (outCount < count) {
1915             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916         }
1917         if(have_sent_ICS_logon == 2) {
1918           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1919             fprintf(ini, "%s", message);
1920             have_sent_ICS_logon = 3;
1921           } else
1922             have_sent_ICS_logon = 1;
1923         } else if(have_sent_ICS_logon == 3) {
1924             fprintf(ini, "%s", message);
1925             fclose(ini);
1926           have_sent_ICS_logon = 1;
1927         }
1928     } else if (count < 0) {
1929         RemoveInputSource(isr);
1930         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1931     } else if (gotEof++ > 0) {
1932         RemoveInputSource(isr);
1933         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1934     }
1935 }
1936
1937 void
1938 KeepAlive ()
1939 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1940     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1941     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1942     SendToICS("date\n");
1943     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1944 }
1945
1946 /* added routine for printf style output to ics */
1947 void
1948 ics_printf (char *format, ...)
1949 {
1950     char buffer[MSG_SIZ];
1951     va_list args;
1952
1953     va_start(args, format);
1954     vsnprintf(buffer, sizeof(buffer), format, args);
1955     buffer[sizeof(buffer)-1] = '\0';
1956     SendToICS(buffer);
1957     va_end(args);
1958 }
1959
1960 void
1961 SendToICS (char *s)
1962 {
1963     int count, outCount, outError;
1964
1965     if (icsPR == NoProc) return;
1966
1967     count = strlen(s);
1968     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1969     if (outCount < count) {
1970         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1971     }
1972 }
1973
1974 /* This is used for sending logon scripts to the ICS. Sending
1975    without a delay causes problems when using timestamp on ICC
1976    (at least on my machine). */
1977 void
1978 SendToICSDelayed (char *s, long msdelay)
1979 {
1980     int count, outCount, outError;
1981
1982     if (icsPR == NoProc) return;
1983
1984     count = strlen(s);
1985     if (appData.debugMode) {
1986         fprintf(debugFP, ">ICS: ");
1987         show_bytes(debugFP, s, count);
1988         fprintf(debugFP, "\n");
1989     }
1990     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1991                                       msdelay);
1992     if (outCount < count) {
1993         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1994     }
1995 }
1996
1997
1998 /* Remove all highlighting escape sequences in s
1999    Also deletes any suffix starting with '('
2000    */
2001 char *
2002 StripHighlightAndTitle (char *s)
2003 {
2004     static char retbuf[MSG_SIZ];
2005     char *p = retbuf;
2006
2007     while (*s != NULLCHAR) {
2008         while (*s == '\033') {
2009             while (*s != NULLCHAR && !isalpha(*s)) s++;
2010             if (*s != NULLCHAR) s++;
2011         }
2012         while (*s != NULLCHAR && *s != '\033') {
2013             if (*s == '(' || *s == '[') {
2014                 *p = NULLCHAR;
2015                 return retbuf;
2016             }
2017             *p++ = *s++;
2018         }
2019     }
2020     *p = NULLCHAR;
2021     return retbuf;
2022 }
2023
2024 /* Remove all highlighting escape sequences in s */
2025 char *
2026 StripHighlight (char *s)
2027 {
2028     static char retbuf[MSG_SIZ];
2029     char *p = retbuf;
2030
2031     while (*s != NULLCHAR) {
2032         while (*s == '\033') {
2033             while (*s != NULLCHAR && !isalpha(*s)) s++;
2034             if (*s != NULLCHAR) s++;
2035         }
2036         while (*s != NULLCHAR && *s != '\033') {
2037             *p++ = *s++;
2038         }
2039     }
2040     *p = NULLCHAR;
2041     return retbuf;
2042 }
2043
2044 char engineVariant[MSG_SIZ];
2045 char *variantNames[] = VARIANT_NAMES;
2046 char *
2047 VariantName (VariantClass v)
2048 {
2049     if(v == VariantUnknown || *engineVariant) return engineVariant;
2050     return variantNames[v];
2051 }
2052
2053
2054 /* Identify a variant from the strings the chess servers use or the
2055    PGN Variant tag names we use. */
2056 VariantClass
2057 StringToVariant (char *e)
2058 {
2059     char *p;
2060     int wnum = -1;
2061     VariantClass v = VariantNormal;
2062     int i, found = FALSE;
2063     char buf[MSG_SIZ];
2064     int len;
2065
2066     if (!e) return v;
2067
2068     /* [HGM] skip over optional board-size prefixes */
2069     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2070         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2071         while( *e++ != '_');
2072     }
2073
2074     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2075         v = VariantNormal;
2076         found = TRUE;
2077     } else
2078     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2079       if (p = StrCaseStr(e, variantNames[i])) {
2080         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2081         v = (VariantClass) i;
2082         found = TRUE;
2083         break;
2084       }
2085     }
2086
2087     if (!found) {
2088       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2089           || StrCaseStr(e, "wild/fr")
2090           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2091         v = VariantFischeRandom;
2092       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2093                  (i = 1, p = StrCaseStr(e, "w"))) {
2094         p += i;
2095         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2096         if (isdigit(*p)) {
2097           wnum = atoi(p);
2098         } else {
2099           wnum = -1;
2100         }
2101         switch (wnum) {
2102         case 0: /* FICS only, actually */
2103         case 1:
2104           /* Castling legal even if K starts on d-file */
2105           v = VariantWildCastle;
2106           break;
2107         case 2:
2108         case 3:
2109         case 4:
2110           /* Castling illegal even if K & R happen to start in
2111              normal positions. */
2112           v = VariantNoCastle;
2113           break;
2114         case 5:
2115         case 7:
2116         case 8:
2117         case 10:
2118         case 11:
2119         case 12:
2120         case 13:
2121         case 14:
2122         case 15:
2123         case 18:
2124         case 19:
2125           /* Castling legal iff K & R start in normal positions */
2126           v = VariantNormal;
2127           break;
2128         case 6:
2129         case 20:
2130         case 21:
2131           /* Special wilds for position setup; unclear what to do here */
2132           v = VariantLoadable;
2133           break;
2134         case 9:
2135           /* Bizarre ICC game */
2136           v = VariantTwoKings;
2137           break;
2138         case 16:
2139           v = VariantKriegspiel;
2140           break;
2141         case 17:
2142           v = VariantLosers;
2143           break;
2144         case 22:
2145           v = VariantFischeRandom;
2146           break;
2147         case 23:
2148           v = VariantCrazyhouse;
2149           break;
2150         case 24:
2151           v = VariantBughouse;
2152           break;
2153         case 25:
2154           v = Variant3Check;
2155           break;
2156         case 26:
2157           /* Not quite the same as FICS suicide! */
2158           v = VariantGiveaway;
2159           break;
2160         case 27:
2161           v = VariantAtomic;
2162           break;
2163         case 28:
2164           v = VariantShatranj;
2165           break;
2166
2167         /* Temporary names for future ICC types.  The name *will* change in
2168            the next xboard/WinBoard release after ICC defines it. */
2169         case 29:
2170           v = Variant29;
2171           break;
2172         case 30:
2173           v = Variant30;
2174           break;
2175         case 31:
2176           v = Variant31;
2177           break;
2178         case 32:
2179           v = Variant32;
2180           break;
2181         case 33:
2182           v = Variant33;
2183           break;
2184         case 34:
2185           v = Variant34;
2186           break;
2187         case 35:
2188           v = Variant35;
2189           break;
2190         case 36:
2191           v = Variant36;
2192           break;
2193         case 37:
2194           v = VariantShogi;
2195           break;
2196         case 38:
2197           v = VariantXiangqi;
2198           break;
2199         case 39:
2200           v = VariantCourier;
2201           break;
2202         case 40:
2203           v = VariantGothic;
2204           break;
2205         case 41:
2206           v = VariantCapablanca;
2207           break;
2208         case 42:
2209           v = VariantKnightmate;
2210           break;
2211         case 43:
2212           v = VariantFairy;
2213           break;
2214         case 44:
2215           v = VariantCylinder;
2216           break;
2217         case 45:
2218           v = VariantFalcon;
2219           break;
2220         case 46:
2221           v = VariantCapaRandom;
2222           break;
2223         case 47:
2224           v = VariantBerolina;
2225           break;
2226         case 48:
2227           v = VariantJanus;
2228           break;
2229         case 49:
2230           v = VariantSuper;
2231           break;
2232         case 50:
2233           v = VariantGreat;
2234           break;
2235         case -1:
2236           /* Found "wild" or "w" in the string but no number;
2237              must assume it's normal chess. */
2238           v = VariantNormal;
2239           break;
2240         default:
2241           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2242           if( (len >= MSG_SIZ) && appData.debugMode )
2243             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2244
2245           DisplayError(buf, 0);
2246           v = VariantUnknown;
2247           break;
2248         }
2249       }
2250     }
2251     if (appData.debugMode) {
2252       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2253               e, wnum, VariantName(v));
2254     }
2255     return v;
2256 }
2257
2258 static int leftover_start = 0, leftover_len = 0;
2259 char star_match[STAR_MATCH_N][MSG_SIZ];
2260
2261 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2262    advance *index beyond it, and set leftover_start to the new value of
2263    *index; else return FALSE.  If pattern contains the character '*', it
2264    matches any sequence of characters not containing '\r', '\n', or the
2265    character following the '*' (if any), and the matched sequence(s) are
2266    copied into star_match.
2267    */
2268 int
2269 looking_at ( char *buf, int *index, char *pattern)
2270 {
2271     char *bufp = &buf[*index], *patternp = pattern;
2272     int star_count = 0;
2273     char *matchp = star_match[0];
2274
2275     for (;;) {
2276         if (*patternp == NULLCHAR) {
2277             *index = leftover_start = bufp - buf;
2278             *matchp = NULLCHAR;
2279             return TRUE;
2280         }
2281         if (*bufp == NULLCHAR) return FALSE;
2282         if (*patternp == '*') {
2283             if (*bufp == *(patternp + 1)) {
2284                 *matchp = NULLCHAR;
2285                 matchp = star_match[++star_count];
2286                 patternp += 2;
2287                 bufp++;
2288                 continue;
2289             } else if (*bufp == '\n' || *bufp == '\r') {
2290                 patternp++;
2291                 if (*patternp == NULLCHAR)
2292                   continue;
2293                 else
2294                   return FALSE;
2295             } else {
2296                 *matchp++ = *bufp++;
2297                 continue;
2298             }
2299         }
2300         if (*patternp != *bufp) return FALSE;
2301         patternp++;
2302         bufp++;
2303     }
2304 }
2305
2306 void
2307 SendToPlayer (char *data, int length)
2308 {
2309     int error, outCount;
2310     outCount = OutputToProcess(NoProc, data, length, &error);
2311     if (outCount < length) {
2312         DisplayFatalError(_("Error writing to display"), error, 1);
2313     }
2314 }
2315
2316 void
2317 PackHolding (char packed[], char *holding)
2318 {
2319     char *p = holding;
2320     char *q = packed;
2321     int runlength = 0;
2322     int curr = 9999;
2323     do {
2324         if (*p == curr) {
2325             runlength++;
2326         } else {
2327             switch (runlength) {
2328               case 0:
2329                 break;
2330               case 1:
2331                 *q++ = curr;
2332                 break;
2333               case 2:
2334                 *q++ = curr;
2335                 *q++ = curr;
2336                 break;
2337               default:
2338                 sprintf(q, "%d", runlength);
2339                 while (*q) q++;
2340                 *q++ = curr;
2341                 break;
2342             }
2343             runlength = 1;
2344             curr = *p;
2345         }
2346     } while (*p++);
2347     *q = NULLCHAR;
2348 }
2349
2350 /* Telnet protocol requests from the front end */
2351 void
2352 TelnetRequest (unsigned char ddww, unsigned char option)
2353 {
2354     unsigned char msg[3];
2355     int outCount, outError;
2356
2357     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2358
2359     if (appData.debugMode) {
2360         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2361         switch (ddww) {
2362           case TN_DO:
2363             ddwwStr = "DO";
2364             break;
2365           case TN_DONT:
2366             ddwwStr = "DONT";
2367             break;
2368           case TN_WILL:
2369             ddwwStr = "WILL";
2370             break;
2371           case TN_WONT:
2372             ddwwStr = "WONT";
2373             break;
2374           default:
2375             ddwwStr = buf1;
2376             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2377             break;
2378         }
2379         switch (option) {
2380           case TN_ECHO:
2381             optionStr = "ECHO";
2382             break;
2383           default:
2384             optionStr = buf2;
2385             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2386             break;
2387         }
2388         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2389     }
2390     msg[0] = TN_IAC;
2391     msg[1] = ddww;
2392     msg[2] = option;
2393     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2394     if (outCount < 3) {
2395         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2396     }
2397 }
2398
2399 void
2400 DoEcho ()
2401 {
2402     if (!appData.icsActive) return;
2403     TelnetRequest(TN_DO, TN_ECHO);
2404 }
2405
2406 void
2407 DontEcho ()
2408 {
2409     if (!appData.icsActive) return;
2410     TelnetRequest(TN_DONT, TN_ECHO);
2411 }
2412
2413 void
2414 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2415 {
2416     /* put the holdings sent to us by the server on the board holdings area */
2417     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2418     char p;
2419     ChessSquare piece;
2420
2421     if(gameInfo.holdingsWidth < 2)  return;
2422     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2423         return; // prevent overwriting by pre-board holdings
2424
2425     if( (int)lowestPiece >= BlackPawn ) {
2426         holdingsColumn = 0;
2427         countsColumn = 1;
2428         holdingsStartRow = BOARD_HEIGHT-1;
2429         direction = -1;
2430     } else {
2431         holdingsColumn = BOARD_WIDTH-1;
2432         countsColumn = BOARD_WIDTH-2;
2433         holdingsStartRow = 0;
2434         direction = 1;
2435     }
2436
2437     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2438         board[i][holdingsColumn] = EmptySquare;
2439         board[i][countsColumn]   = (ChessSquare) 0;
2440     }
2441     while( (p=*holdings++) != NULLCHAR ) {
2442         piece = CharToPiece( ToUpper(p) );
2443         if(piece == EmptySquare) continue;
2444         /*j = (int) piece - (int) WhitePawn;*/
2445         j = PieceToNumber(piece);
2446         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2447         if(j < 0) continue;               /* should not happen */
2448         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2449         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2450         board[holdingsStartRow+j*direction][countsColumn]++;
2451     }
2452 }
2453
2454
2455 void
2456 VariantSwitch (Board board, VariantClass newVariant)
2457 {
2458    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2459    static Board oldBoard;
2460
2461    startedFromPositionFile = FALSE;
2462    if(gameInfo.variant == newVariant) return;
2463
2464    /* [HGM] This routine is called each time an assignment is made to
2465     * gameInfo.variant during a game, to make sure the board sizes
2466     * are set to match the new variant. If that means adding or deleting
2467     * holdings, we shift the playing board accordingly
2468     * This kludge is needed because in ICS observe mode, we get boards
2469     * of an ongoing game without knowing the variant, and learn about the
2470     * latter only later. This can be because of the move list we requested,
2471     * in which case the game history is refilled from the beginning anyway,
2472     * but also when receiving holdings of a crazyhouse game. In the latter
2473     * case we want to add those holdings to the already received position.
2474     */
2475
2476
2477    if (appData.debugMode) {
2478      fprintf(debugFP, "Switch board from %s to %s\n",
2479              VariantName(gameInfo.variant), VariantName(newVariant));
2480      setbuf(debugFP, NULL);
2481    }
2482    shuffleOpenings = 0;       /* [HGM] shuffle */
2483    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2484    switch(newVariant)
2485      {
2486      case VariantShogi:
2487        newWidth = 9;  newHeight = 9;
2488        gameInfo.holdingsSize = 7;
2489      case VariantBughouse:
2490      case VariantCrazyhouse:
2491        newHoldingsWidth = 2; break;
2492      case VariantGreat:
2493        newWidth = 10;
2494      case VariantSuper:
2495        newHoldingsWidth = 2;
2496        gameInfo.holdingsSize = 8;
2497        break;
2498      case VariantGothic:
2499      case VariantCapablanca:
2500      case VariantCapaRandom:
2501        newWidth = 10;
2502      default:
2503        newHoldingsWidth = gameInfo.holdingsSize = 0;
2504      };
2505
2506    if(newWidth  != gameInfo.boardWidth  ||
2507       newHeight != gameInfo.boardHeight ||
2508       newHoldingsWidth != gameInfo.holdingsWidth ) {
2509
2510      /* shift position to new playing area, if needed */
2511      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2512        for(i=0; i<BOARD_HEIGHT; i++)
2513          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2514            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2515              board[i][j];
2516        for(i=0; i<newHeight; i++) {
2517          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2518          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2519        }
2520      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2521        for(i=0; i<BOARD_HEIGHT; i++)
2522          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2523            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2524              board[i][j];
2525      }
2526      board[HOLDINGS_SET] = 0;
2527      gameInfo.boardWidth  = newWidth;
2528      gameInfo.boardHeight = newHeight;
2529      gameInfo.holdingsWidth = newHoldingsWidth;
2530      gameInfo.variant = newVariant;
2531      InitDrawingSizes(-2, 0);
2532    } else gameInfo.variant = newVariant;
2533    CopyBoard(oldBoard, board);   // remember correctly formatted board
2534      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2535    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2536 }
2537
2538 static int loggedOn = FALSE;
2539
2540 /*-- Game start info cache: --*/
2541 int gs_gamenum;
2542 char gs_kind[MSG_SIZ];
2543 static char player1Name[128] = "";
2544 static char player2Name[128] = "";
2545 static char cont_seq[] = "\n\\   ";
2546 static int player1Rating = -1;
2547 static int player2Rating = -1;
2548 /*----------------------------*/
2549
2550 ColorClass curColor = ColorNormal;
2551 int suppressKibitz = 0;
2552
2553 // [HGM] seekgraph
2554 Boolean soughtPending = FALSE;
2555 Boolean seekGraphUp;
2556 #define MAX_SEEK_ADS 200
2557 #define SQUARE 0x80
2558 char *seekAdList[MAX_SEEK_ADS];
2559 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2560 float tcList[MAX_SEEK_ADS];
2561 char colorList[MAX_SEEK_ADS];
2562 int nrOfSeekAds = 0;
2563 int minRating = 1010, maxRating = 2800;
2564 int hMargin = 10, vMargin = 20, h, w;
2565 extern int squareSize, lineGap;
2566
2567 void
2568 PlotSeekAd (int i)
2569 {
2570         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2571         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2572         if(r < minRating+100 && r >=0 ) r = minRating+100;
2573         if(r > maxRating) r = maxRating;
2574         if(tc < 1.f) tc = 1.f;
2575         if(tc > 95.f) tc = 95.f;
2576         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2577         y = ((double)r - minRating)/(maxRating - minRating)
2578             * (h-vMargin-squareSize/8-1) + vMargin;
2579         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2580         if(strstr(seekAdList[i], " u ")) color = 1;
2581         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2582            !strstr(seekAdList[i], "bullet") &&
2583            !strstr(seekAdList[i], "blitz") &&
2584            !strstr(seekAdList[i], "standard") ) color = 2;
2585         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2586         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2587 }
2588
2589 void
2590 PlotSingleSeekAd (int i)
2591 {
2592         PlotSeekAd(i);
2593 }
2594
2595 void
2596 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2597 {
2598         char buf[MSG_SIZ], *ext = "";
2599         VariantClass v = StringToVariant(type);
2600         if(strstr(type, "wild")) {
2601             ext = type + 4; // append wild number
2602             if(v == VariantFischeRandom) type = "chess960"; else
2603             if(v == VariantLoadable) type = "setup"; else
2604             type = VariantName(v);
2605         }
2606         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2607         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2608             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2609             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2610             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2611             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2612             seekNrList[nrOfSeekAds] = nr;
2613             zList[nrOfSeekAds] = 0;
2614             seekAdList[nrOfSeekAds++] = StrSave(buf);
2615             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2616         }
2617 }
2618
2619 void
2620 EraseSeekDot (int i)
2621 {
2622     int x = xList[i], y = yList[i], d=squareSize/4, k;
2623     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2624     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2625     // now replot every dot that overlapped
2626     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2627         int xx = xList[k], yy = yList[k];
2628         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2629             DrawSeekDot(xx, yy, colorList[k]);
2630     }
2631 }
2632
2633 void
2634 RemoveSeekAd (int nr)
2635 {
2636         int i;
2637         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2638             EraseSeekDot(i);
2639             if(seekAdList[i]) free(seekAdList[i]);
2640             seekAdList[i] = seekAdList[--nrOfSeekAds];
2641             seekNrList[i] = seekNrList[nrOfSeekAds];
2642             ratingList[i] = ratingList[nrOfSeekAds];
2643             colorList[i]  = colorList[nrOfSeekAds];
2644             tcList[i] = tcList[nrOfSeekAds];
2645             xList[i]  = xList[nrOfSeekAds];
2646             yList[i]  = yList[nrOfSeekAds];
2647             zList[i]  = zList[nrOfSeekAds];
2648             seekAdList[nrOfSeekAds] = NULL;
2649             break;
2650         }
2651 }
2652
2653 Boolean
2654 MatchSoughtLine (char *line)
2655 {
2656     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2657     int nr, base, inc, u=0; char dummy;
2658
2659     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2660        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2661        (u=1) &&
2662        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2663         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2664         // match: compact and save the line
2665         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2666         return TRUE;
2667     }
2668     return FALSE;
2669 }
2670
2671 int
2672 DrawSeekGraph ()
2673 {
2674     int i;
2675     if(!seekGraphUp) return FALSE;
2676     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2677     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2678
2679     DrawSeekBackground(0, 0, w, h);
2680     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2681     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2682     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2683         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2684         yy = h-1-yy;
2685         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2686         if(i%500 == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2690         }
2691     }
2692     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2693     for(i=1; i<100; i+=(i<10?1:5)) {
2694         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2695         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2696         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2697             char buf[MSG_SIZ];
2698             snprintf(buf, MSG_SIZ, "%d", i);
2699             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2700         }
2701     }
2702     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2703     return TRUE;
2704 }
2705
2706 int
2707 SeekGraphClick (ClickType click, int x, int y, int moving)
2708 {
2709     static int lastDown = 0, displayed = 0, lastSecond;
2710     if(y < 0) return FALSE;
2711     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2712         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2713         if(!seekGraphUp) return FALSE;
2714         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2715         DrawPosition(TRUE, NULL);
2716         return TRUE;
2717     }
2718     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2719         if(click == Release || moving) return FALSE;
2720         nrOfSeekAds = 0;
2721         soughtPending = TRUE;
2722         SendToICS(ics_prefix);
2723         SendToICS("sought\n"); // should this be "sought all"?
2724     } else { // issue challenge based on clicked ad
2725         int dist = 10000; int i, closest = 0, second = 0;
2726         for(i=0; i<nrOfSeekAds; i++) {
2727             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2728             if(d < dist) { dist = d; closest = i; }
2729             second += (d - zList[i] < 120); // count in-range ads
2730             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2731         }
2732         if(dist < 120) {
2733             char buf[MSG_SIZ];
2734             second = (second > 1);
2735             if(displayed != closest || second != lastSecond) {
2736                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2737                 lastSecond = second; displayed = closest;
2738             }
2739             if(click == Press) {
2740                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2741                 lastDown = closest;
2742                 return TRUE;
2743             } // on press 'hit', only show info
2744             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2745             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2746             SendToICS(ics_prefix);
2747             SendToICS(buf);
2748             return TRUE; // let incoming board of started game pop down the graph
2749         } else if(click == Release) { // release 'miss' is ignored
2750             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2751             if(moving == 2) { // right up-click
2752                 nrOfSeekAds = 0; // refresh graph
2753                 soughtPending = TRUE;
2754                 SendToICS(ics_prefix);
2755                 SendToICS("sought\n"); // should this be "sought all"?
2756             }
2757             return TRUE;
2758         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2759         // press miss or release hit 'pop down' seek graph
2760         seekGraphUp = FALSE;
2761         DrawPosition(TRUE, NULL);
2762     }
2763     return TRUE;
2764 }
2765
2766 void
2767 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2768 {
2769 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2770 #define STARTED_NONE 0
2771 #define STARTED_MOVES 1
2772 #define STARTED_BOARD 2
2773 #define STARTED_OBSERVE 3
2774 #define STARTED_HOLDINGS 4
2775 #define STARTED_CHATTER 5
2776 #define STARTED_COMMENT 6
2777 #define STARTED_MOVES_NOHIDE 7
2778
2779     static int started = STARTED_NONE;
2780     static char parse[20000];
2781     static int parse_pos = 0;
2782     static char buf[BUF_SIZE + 1];
2783     static int firstTime = TRUE, intfSet = FALSE;
2784     static ColorClass prevColor = ColorNormal;
2785     static int savingComment = FALSE;
2786     static int cmatch = 0; // continuation sequence match
2787     char *bp;
2788     char str[MSG_SIZ];
2789     int i, oldi;
2790     int buf_len;
2791     int next_out;
2792     int tkind;
2793     int backup;    /* [DM] For zippy color lines */
2794     char *p;
2795     char talker[MSG_SIZ]; // [HGM] chat
2796     int channel;
2797
2798     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2799
2800     if (appData.debugMode) {
2801       if (!error) {
2802         fprintf(debugFP, "<ICS: ");
2803         show_bytes(debugFP, data, count);
2804         fprintf(debugFP, "\n");
2805       }
2806     }
2807
2808     if (appData.debugMode) { int f = forwardMostMove;
2809         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2810                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2811                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2812     }
2813     if (count > 0) {
2814         /* If last read ended with a partial line that we couldn't parse,
2815            prepend it to the new read and try again. */
2816         if (leftover_len > 0) {
2817             for (i=0; i<leftover_len; i++)
2818               buf[i] = buf[leftover_start + i];
2819         }
2820
2821     /* copy new characters into the buffer */
2822     bp = buf + leftover_len;
2823     buf_len=leftover_len;
2824     for (i=0; i<count; i++)
2825     {
2826         // ignore these
2827         if (data[i] == '\r')
2828             continue;
2829
2830         // join lines split by ICS?
2831         if (!appData.noJoin)
2832         {
2833             /*
2834                 Joining just consists of finding matches against the
2835                 continuation sequence, and discarding that sequence
2836                 if found instead of copying it.  So, until a match
2837                 fails, there's nothing to do since it might be the
2838                 complete sequence, and thus, something we don't want
2839                 copied.
2840             */
2841             if (data[i] == cont_seq[cmatch])
2842             {
2843                 cmatch++;
2844                 if (cmatch == strlen(cont_seq))
2845                 {
2846                     cmatch = 0; // complete match.  just reset the counter
2847
2848                     /*
2849                         it's possible for the ICS to not include the space
2850                         at the end of the last word, making our [correct]
2851                         join operation fuse two separate words.  the server
2852                         does this when the space occurs at the width setting.
2853                     */
2854                     if (!buf_len || buf[buf_len-1] != ' ')
2855                     {
2856                         *bp++ = ' ';
2857                         buf_len++;
2858                     }
2859                 }
2860                 continue;
2861             }
2862             else if (cmatch)
2863             {
2864                 /*
2865                     match failed, so we have to copy what matched before
2866                     falling through and copying this character.  In reality,
2867                     this will only ever be just the newline character, but
2868                     it doesn't hurt to be precise.
2869                 */
2870                 strncpy(bp, cont_seq, cmatch);
2871                 bp += cmatch;
2872                 buf_len += cmatch;
2873                 cmatch = 0;
2874             }
2875         }
2876
2877         // copy this char
2878         *bp++ = data[i];
2879         buf_len++;
2880     }
2881
2882         buf[buf_len] = NULLCHAR;
2883 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2884         next_out = 0;
2885         leftover_start = 0;
2886
2887         i = 0;
2888         while (i < buf_len) {
2889             /* Deal with part of the TELNET option negotiation
2890                protocol.  We refuse to do anything beyond the
2891                defaults, except that we allow the WILL ECHO option,
2892                which ICS uses to turn off password echoing when we are
2893                directly connected to it.  We reject this option
2894                if localLineEditing mode is on (always on in xboard)
2895                and we are talking to port 23, which might be a real
2896                telnet server that will try to keep WILL ECHO on permanently.
2897              */
2898             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2899                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2900                 unsigned char option;
2901                 oldi = i;
2902                 switch ((unsigned char) buf[++i]) {
2903                   case TN_WILL:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<WILL ");
2906                     switch (option = (unsigned char) buf[++i]) {
2907                       case TN_ECHO:
2908                         if (appData.debugMode)
2909                           fprintf(debugFP, "ECHO ");
2910                         /* Reply only if this is a change, according
2911                            to the protocol rules. */
2912                         if (remoteEchoOption) break;
2913                         if (appData.localLineEditing &&
2914                             atoi(appData.icsPort) == TN_PORT) {
2915                             TelnetRequest(TN_DONT, TN_ECHO);
2916                         } else {
2917                             EchoOff();
2918                             TelnetRequest(TN_DO, TN_ECHO);
2919                             remoteEchoOption = TRUE;
2920                         }
2921                         break;
2922                       default:
2923                         if (appData.debugMode)
2924                           fprintf(debugFP, "%d ", option);
2925                         /* Whatever this is, we don't want it. */
2926                         TelnetRequest(TN_DONT, option);
2927                         break;
2928                     }
2929                     break;
2930                   case TN_WONT:
2931                     if (appData.debugMode)
2932                       fprintf(debugFP, "\n<WONT ");
2933                     switch (option = (unsigned char) buf[++i]) {
2934                       case TN_ECHO:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "ECHO ");
2937                         /* Reply only if this is a change, according
2938                            to the protocol rules. */
2939                         if (!remoteEchoOption) break;
2940                         EchoOn();
2941                         TelnetRequest(TN_DONT, TN_ECHO);
2942                         remoteEchoOption = FALSE;
2943                         break;
2944                       default:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "%d ", (unsigned char) option);
2947                         /* Whatever this is, it must already be turned
2948                            off, because we never agree to turn on
2949                            anything non-default, so according to the
2950                            protocol rules, we don't reply. */
2951                         break;
2952                     }
2953                     break;
2954                   case TN_DO:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<DO ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       default:
2959                         /* Whatever this is, we refuse to do it. */
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         TelnetRequest(TN_WONT, option);
2963                         break;
2964                     }
2965                     break;
2966                   case TN_DONT:
2967                     if (appData.debugMode)
2968                       fprintf(debugFP, "\n<DONT ");
2969                     switch (option = (unsigned char) buf[++i]) {
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we are already not doing
2974                            it, because we never agree to do anything
2975                            non-default, so according to the protocol
2976                            rules, we don't reply. */
2977                         break;
2978                     }
2979                     break;
2980                   case TN_IAC:
2981                     if (appData.debugMode)
2982                       fprintf(debugFP, "\n<IAC ");
2983                     /* Doubled IAC; pass it through */
2984                     i--;
2985                     break;
2986                   default:
2987                     if (appData.debugMode)
2988                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2989                     /* Drop all other telnet commands on the floor */
2990                     break;
2991                 }
2992                 if (oldi > next_out)
2993                   SendToPlayer(&buf[next_out], oldi - next_out);
2994                 if (++i > next_out)
2995                   next_out = i;
2996                 continue;
2997             }
2998
2999             /* OK, this at least will *usually* work */
3000             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3001                 loggedOn = TRUE;
3002             }
3003
3004             if (loggedOn && !intfSet) {
3005                 if (ics_type == ICS_ICC) {
3006                   snprintf(str, MSG_SIZ,
3007                           "/set-quietly interface %s\n/set-quietly style 12\n",
3008                           programVersion);
3009                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3010                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3011                 } else if (ics_type == ICS_CHESSNET) {
3012                   snprintf(str, MSG_SIZ, "/style 12\n");
3013                 } else {
3014                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3015                   strcat(str, programVersion);
3016                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3017                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3018                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3019 #ifdef WIN32
3020                   strcat(str, "$iset nohighlight 1\n");
3021 #endif
3022                   strcat(str, "$iset lock 1\n$style 12\n");
3023                 }
3024                 SendToICS(str);
3025                 NotifyFrontendLogin();
3026                 intfSet = TRUE;
3027             }
3028
3029             if (started == STARTED_COMMENT) {
3030                 /* Accumulate characters in comment */
3031                 parse[parse_pos++] = buf[i];
3032                 if (buf[i] == '\n') {
3033                     parse[parse_pos] = NULLCHAR;
3034                     if(chattingPartner>=0) {
3035                         char mess[MSG_SIZ];
3036                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3037                         OutputChatMessage(chattingPartner, mess);
3038                         chattingPartner = -1;
3039                         next_out = i+1; // [HGM] suppress printing in ICS window
3040                     } else
3041                     if(!suppressKibitz) // [HGM] kibitz
3042                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3043                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3044                         int nrDigit = 0, nrAlph = 0, j;
3045                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3046                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3047                         parse[parse_pos] = NULLCHAR;
3048                         // try to be smart: if it does not look like search info, it should go to
3049                         // ICS interaction window after all, not to engine-output window.
3050                         for(j=0; j<parse_pos; j++) { // count letters and digits
3051                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3052                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3053                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3054                         }
3055                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3056                             int depth=0; float score;
3057                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3058                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3059                                 pvInfoList[forwardMostMove-1].depth = depth;
3060                                 pvInfoList[forwardMostMove-1].score = 100*score;
3061                             }
3062                             OutputKibitz(suppressKibitz, parse);
3063                         } else {
3064                             char tmp[MSG_SIZ];
3065                             if(gameMode == IcsObserving) // restore original ICS messages
3066                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3067                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3068                             else
3069                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3070                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3071                             SendToPlayer(tmp, strlen(tmp));
3072                         }
3073                         next_out = i+1; // [HGM] suppress printing in ICS window
3074                     }
3075                     started = STARTED_NONE;
3076                 } else {
3077                     /* Don't match patterns against characters in comment */
3078                     i++;
3079                     continue;
3080                 }
3081             }
3082             if (started == STARTED_CHATTER) {
3083                 if (buf[i] != '\n') {
3084                     /* Don't match patterns against characters in chatter */
3085                     i++;
3086                     continue;
3087                 }
3088                 started = STARTED_NONE;
3089                 if(suppressKibitz) next_out = i+1;
3090             }
3091
3092             /* Kludge to deal with rcmd protocol */
3093             if (firstTime && looking_at(buf, &i, "\001*")) {
3094                 DisplayFatalError(&buf[1], 0, 1);
3095                 continue;
3096             } else {
3097                 firstTime = FALSE;
3098             }
3099
3100             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3101                 ics_type = ICS_ICC;
3102                 ics_prefix = "/";
3103                 if (appData.debugMode)
3104                   fprintf(debugFP, "ics_type %d\n", ics_type);
3105                 continue;
3106             }
3107             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3108                 ics_type = ICS_FICS;
3109                 ics_prefix = "$";
3110                 if (appData.debugMode)
3111                   fprintf(debugFP, "ics_type %d\n", ics_type);
3112                 continue;
3113             }
3114             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3115                 ics_type = ICS_CHESSNET;
3116                 ics_prefix = "/";
3117                 if (appData.debugMode)
3118                   fprintf(debugFP, "ics_type %d\n", ics_type);
3119                 continue;
3120             }
3121
3122             if (!loggedOn &&
3123                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3124                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3125                  looking_at(buf, &i, "will be \"*\""))) {
3126               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3127               continue;
3128             }
3129
3130             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3131               char buf[MSG_SIZ];
3132               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3133               DisplayIcsInteractionTitle(buf);
3134               have_set_title = TRUE;
3135             }
3136
3137             /* skip finger notes */
3138             if (started == STARTED_NONE &&
3139                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3140                  (buf[i] == '1' && buf[i+1] == '0')) &&
3141                 buf[i+2] == ':' && buf[i+3] == ' ') {
3142               started = STARTED_CHATTER;
3143               i += 3;
3144               continue;
3145             }
3146
3147             oldi = i;
3148             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3149             if(appData.seekGraph) {
3150                 if(soughtPending && MatchSoughtLine(buf+i)) {
3151                     i = strstr(buf+i, "rated") - buf;
3152                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153                     next_out = leftover_start = i;
3154                     started = STARTED_CHATTER;
3155                     suppressKibitz = TRUE;
3156                     continue;
3157                 }
3158                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3159                         && looking_at(buf, &i, "* ads displayed")) {
3160                     soughtPending = FALSE;
3161                     seekGraphUp = TRUE;
3162                     DrawSeekGraph();
3163                     continue;
3164                 }
3165                 if(appData.autoRefresh) {
3166                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3167                         int s = (ics_type == ICS_ICC); // ICC format differs
3168                         if(seekGraphUp)
3169                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3170                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3171                         looking_at(buf, &i, "*% "); // eat prompt
3172                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3173                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3174                         next_out = i; // suppress
3175                         continue;
3176                     }
3177                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3178                         char *p = star_match[0];
3179                         while(*p) {
3180                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3181                             while(*p && *p++ != ' '); // next
3182                         }
3183                         looking_at(buf, &i, "*% "); // eat prompt
3184                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3185                         next_out = i;
3186                         continue;
3187                     }
3188                 }
3189             }
3190
3191             /* skip formula vars */
3192             if (started == STARTED_NONE &&
3193                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3194               started = STARTED_CHATTER;
3195               i += 3;
3196               continue;
3197             }
3198
3199             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3200             if (appData.autoKibitz && started == STARTED_NONE &&
3201                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3202                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3203                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3204                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3205                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3206                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3207                         suppressKibitz = TRUE;
3208                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3209                         next_out = i;
3210                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3211                                 && (gameMode == IcsPlayingWhite)) ||
3212                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3213                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3214                             started = STARTED_CHATTER; // own kibitz we simply discard
3215                         else {
3216                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3217                             parse_pos = 0; parse[0] = NULLCHAR;
3218                             savingComment = TRUE;
3219                             suppressKibitz = gameMode != IcsObserving ? 2 :
3220                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3221                         }
3222                         continue;
3223                 } else
3224                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3225                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3226                          && atoi(star_match[0])) {
3227                     // suppress the acknowledgements of our own autoKibitz
3228                     char *p;
3229                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3230                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3231                     SendToPlayer(star_match[0], strlen(star_match[0]));
3232                     if(looking_at(buf, &i, "*% ")) // eat prompt
3233                         suppressKibitz = FALSE;
3234                     next_out = i;
3235                     continue;
3236                 }
3237             } // [HGM] kibitz: end of patch
3238
3239             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3240
3241             // [HGM] chat: intercept tells by users for which we have an open chat window
3242             channel = -1;
3243             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3244                                            looking_at(buf, &i, "* whispers:") ||
3245                                            looking_at(buf, &i, "* kibitzes:") ||
3246                                            looking_at(buf, &i, "* shouts:") ||
3247                                            looking_at(buf, &i, "* c-shouts:") ||
3248                                            looking_at(buf, &i, "--> * ") ||
3249                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3250                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3251                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3252                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3253                 int p;
3254                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3255                 chattingPartner = -1;
3256
3257                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3258                 for(p=0; p<MAX_CHAT; p++) {
3259                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3260                     talker[0] = '['; strcat(talker, "] ");
3261                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3262                     chattingPartner = p; break;
3263                     }
3264                 } else
3265                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3266                 for(p=0; p<MAX_CHAT; p++) {
3267                     if(!strcmp("kibitzes", chatPartner[p])) {
3268                         talker[0] = '['; strcat(talker, "] ");
3269                         chattingPartner = p; break;
3270                     }
3271                 } else
3272                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3273                 for(p=0; p<MAX_CHAT; p++) {
3274                     if(!strcmp("whispers", chatPartner[p])) {
3275                         talker[0] = '['; strcat(talker, "] ");
3276                         chattingPartner = p; break;
3277                     }
3278                 } else
3279                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3280                   if(buf[i-8] == '-' && buf[i-3] == 't')
3281                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3282                     if(!strcmp("c-shouts", chatPartner[p])) {
3283                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3284                         chattingPartner = p; break;
3285                     }
3286                   }
3287                   if(chattingPartner < 0)
3288                   for(p=0; p<MAX_CHAT; p++) {
3289                     if(!strcmp("shouts", chatPartner[p])) {
3290                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3291                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3292                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3293                         chattingPartner = p; break;
3294                     }
3295                   }
3296                 }
3297                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3298                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3299                     talker[0] = 0; Colorize(ColorTell, FALSE);
3300                     chattingPartner = p; break;
3301                 }
3302                 if(chattingPartner<0) i = oldi; else {
3303                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3304                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3305                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3306                     started = STARTED_COMMENT;
3307                     parse_pos = 0; parse[0] = NULLCHAR;
3308                     savingComment = 3 + chattingPartner; // counts as TRUE
3309                     suppressKibitz = TRUE;
3310                     continue;
3311                 }
3312             } // [HGM] chat: end of patch
3313
3314           backup = i;
3315             if (appData.zippyTalk || appData.zippyPlay) {
3316                 /* [DM] Backup address for color zippy lines */
3317 #if ZIPPY
3318                if (loggedOn == TRUE)
3319                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3320                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3321 #endif
3322             } // [DM] 'else { ' deleted
3323                 if (
3324                     /* Regular tells and says */
3325                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3326                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3327                     looking_at(buf, &i, "* says: ") ||
3328                     /* Don't color "message" or "messages" output */
3329                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3330                     looking_at(buf, &i, "*. * at *:*: ") ||
3331                     looking_at(buf, &i, "--* (*:*): ") ||
3332                     /* Message notifications (same color as tells) */
3333                     looking_at(buf, &i, "* has left a message ") ||
3334                     looking_at(buf, &i, "* just sent you a message:\n") ||
3335                     /* Whispers and kibitzes */
3336                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3337                     looking_at(buf, &i, "* kibitzes: ") ||
3338                     /* Channel tells */
3339                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3340
3341                   if (tkind == 1 && strchr(star_match[0], ':')) {
3342                       /* Avoid "tells you:" spoofs in channels */
3343                      tkind = 3;
3344                   }
3345                   if (star_match[0][0] == NULLCHAR ||
3346                       strchr(star_match[0], ' ') ||
3347                       (tkind == 3 && strchr(star_match[1], ' '))) {
3348                     /* Reject bogus matches */
3349                     i = oldi;
3350                   } else {
3351                     if (appData.colorize) {
3352                       if (oldi > next_out) {
3353                         SendToPlayer(&buf[next_out], oldi - next_out);
3354                         next_out = oldi;
3355                       }
3356                       switch (tkind) {
3357                       case 1:
3358                         Colorize(ColorTell, FALSE);
3359                         curColor = ColorTell;
3360                         break;
3361                       case 2:
3362                         Colorize(ColorKibitz, FALSE);
3363                         curColor = ColorKibitz;
3364                         break;
3365                       case 3:
3366                         p = strrchr(star_match[1], '(');
3367                         if (p == NULL) {
3368                           p = star_match[1];
3369                         } else {
3370                           p++;
3371                         }
3372                         if (atoi(p) == 1) {
3373                           Colorize(ColorChannel1, FALSE);
3374                           curColor = ColorChannel1;
3375                         } else {
3376                           Colorize(ColorChannel, FALSE);
3377                           curColor = ColorChannel;
3378                         }
3379                         break;
3380                       case 5:
3381                         curColor = ColorNormal;
3382                         break;
3383                       }
3384                     }
3385                     if (started == STARTED_NONE && appData.autoComment &&
3386                         (gameMode == IcsObserving ||
3387                          gameMode == IcsPlayingWhite ||
3388                          gameMode == IcsPlayingBlack)) {
3389                       parse_pos = i - oldi;
3390                       memcpy(parse, &buf[oldi], parse_pos);
3391                       parse[parse_pos] = NULLCHAR;
3392                       started = STARTED_COMMENT;
3393                       savingComment = TRUE;
3394                     } else {
3395                       started = STARTED_CHATTER;
3396                       savingComment = FALSE;
3397                     }
3398                     loggedOn = TRUE;
3399                     continue;
3400                   }
3401                 }
3402
3403                 if (looking_at(buf, &i, "* s-shouts: ") ||
3404                     looking_at(buf, &i, "* c-shouts: ")) {
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorSShout, FALSE);
3411                         curColor = ColorSShout;
3412                     }
3413                     loggedOn = TRUE;
3414                     started = STARTED_CHATTER;
3415                     continue;
3416                 }
3417
3418                 if (looking_at(buf, &i, "--->")) {
3419                     loggedOn = TRUE;
3420                     continue;
3421                 }
3422
3423                 if (looking_at(buf, &i, "* shouts: ") ||
3424                     looking_at(buf, &i, "--> ")) {
3425                     if (appData.colorize) {
3426                         if (oldi > next_out) {
3427                             SendToPlayer(&buf[next_out], oldi - next_out);
3428                             next_out = oldi;
3429                         }
3430                         Colorize(ColorShout, FALSE);
3431                         curColor = ColorShout;
3432                     }
3433                     loggedOn = TRUE;
3434                     started = STARTED_CHATTER;
3435                     continue;
3436                 }
3437
3438                 if (looking_at( buf, &i, "Challenge:")) {
3439                     if (appData.colorize) {
3440                         if (oldi > next_out) {
3441                             SendToPlayer(&buf[next_out], oldi - next_out);
3442                             next_out = oldi;
3443                         }
3444                         Colorize(ColorChallenge, FALSE);
3445                         curColor = ColorChallenge;
3446                     }
3447                     loggedOn = TRUE;
3448                     continue;
3449                 }
3450
3451                 if (looking_at(buf, &i, "* offers you") ||
3452                     looking_at(buf, &i, "* offers to be") ||
3453                     looking_at(buf, &i, "* would like to") ||
3454                     looking_at(buf, &i, "* requests to") ||
3455                     looking_at(buf, &i, "Your opponent offers") ||
3456                     looking_at(buf, &i, "Your opponent requests")) {
3457
3458                     if (appData.colorize) {
3459                         if (oldi > next_out) {
3460                             SendToPlayer(&buf[next_out], oldi - next_out);
3461                             next_out = oldi;
3462                         }
3463                         Colorize(ColorRequest, FALSE);
3464                         curColor = ColorRequest;
3465                     }
3466                     continue;
3467                 }
3468
3469                 if (looking_at(buf, &i, "* (*) seeking")) {
3470                     if (appData.colorize) {
3471                         if (oldi > next_out) {
3472                             SendToPlayer(&buf[next_out], oldi - next_out);
3473                             next_out = oldi;
3474                         }
3475                         Colorize(ColorSeek, FALSE);
3476                         curColor = ColorSeek;
3477                     }
3478                     continue;
3479             }
3480
3481           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3482
3483             if (looking_at(buf, &i, "\\   ")) {
3484                 if (prevColor != ColorNormal) {
3485                     if (oldi > next_out) {
3486                         SendToPlayer(&buf[next_out], oldi - next_out);
3487                         next_out = oldi;
3488                     }
3489                     Colorize(prevColor, TRUE);
3490                     curColor = prevColor;
3491                 }
3492                 if (savingComment) {
3493                     parse_pos = i - oldi;
3494                     memcpy(parse, &buf[oldi], parse_pos);
3495                     parse[parse_pos] = NULLCHAR;
3496                     started = STARTED_COMMENT;
3497                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3498                         chattingPartner = savingComment - 3; // kludge to remember the box
3499                 } else {
3500                     started = STARTED_CHATTER;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "Black Strength :") ||
3506                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3507                 looking_at(buf, &i, "<10>") ||
3508                 looking_at(buf, &i, "#@#")) {
3509                 /* Wrong board style */
3510                 loggedOn = TRUE;
3511                 SendToICS(ics_prefix);
3512                 SendToICS("set style 12\n");
3513                 SendToICS(ics_prefix);
3514                 SendToICS("refresh\n");
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "login:")) {
3519               if (!have_sent_ICS_logon) {
3520                 if(ICSInitScript())
3521                   have_sent_ICS_logon = 1;
3522                 else // no init script was found
3523                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3524               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3525                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3526               }
3527                 continue;
3528             }
3529
3530             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3531                 (looking_at(buf, &i, "\n<12> ") ||
3532                  looking_at(buf, &i, "<12> "))) {
3533                 loggedOn = TRUE;
3534                 if (oldi > next_out) {
3535                     SendToPlayer(&buf[next_out], oldi - next_out);
3536                 }
3537                 next_out = i;
3538                 started = STARTED_BOARD;
3539                 parse_pos = 0;
3540                 continue;
3541             }
3542
3543             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3544                 looking_at(buf, &i, "<b1> ")) {
3545                 if (oldi > next_out) {
3546                     SendToPlayer(&buf[next_out], oldi - next_out);
3547                 }
3548                 next_out = i;
3549                 started = STARTED_HOLDINGS;
3550                 parse_pos = 0;
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3555                 loggedOn = TRUE;
3556                 /* Header for a move list -- first line */
3557
3558                 switch (ics_getting_history) {
3559                   case H_FALSE:
3560                     switch (gameMode) {
3561                       case IcsIdle:
3562                       case BeginningOfGame:
3563                         /* User typed "moves" or "oldmoves" while we
3564                            were idle.  Pretend we asked for these
3565                            moves and soak them up so user can step
3566                            through them and/or save them.
3567                            */
3568                         Reset(FALSE, TRUE);
3569                         gameMode = IcsObserving;
3570                         ModeHighlight();
3571                         ics_gamenum = -1;
3572                         ics_getting_history = H_GOT_UNREQ_HEADER;
3573                         break;
3574                       case EditGame: /*?*/
3575                       case EditPosition: /*?*/
3576                         /* Should above feature work in these modes too? */
3577                         /* For now it doesn't */
3578                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3579                         break;
3580                       default:
3581                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3582                         break;
3583                     }
3584                     break;
3585                   case H_REQUESTED:
3586                     /* Is this the right one? */
3587                     if (gameInfo.white && gameInfo.black &&
3588                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3589                         strcmp(gameInfo.black, star_match[2]) == 0) {
3590                         /* All is well */
3591                         ics_getting_history = H_GOT_REQ_HEADER;
3592                     }
3593                     break;
3594                   case H_GOT_REQ_HEADER:
3595                   case H_GOT_UNREQ_HEADER:
3596                   case H_GOT_UNWANTED_HEADER:
3597                   case H_GETTING_MOVES:
3598                     /* Should not happen */
3599                     DisplayError(_("Error gathering move list: two headers"), 0);
3600                     ics_getting_history = H_FALSE;
3601                     break;
3602                 }
3603
3604                 /* Save player ratings into gameInfo if needed */
3605                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3606                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3607                     (gameInfo.whiteRating == -1 ||
3608                      gameInfo.blackRating == -1)) {
3609
3610                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3611                     gameInfo.blackRating = string_to_rating(star_match[3]);
3612                     if (appData.debugMode)
3613                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3614                               gameInfo.whiteRating, gameInfo.blackRating);
3615                 }
3616                 continue;
3617             }
3618
3619             if (looking_at(buf, &i,
3620               "* * match, initial time: * minute*, increment: * second")) {
3621                 /* Header for a move list -- second line */
3622                 /* Initial board will follow if this is a wild game */
3623                 if (gameInfo.event != NULL) free(gameInfo.event);
3624                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3625                 gameInfo.event = StrSave(str);
3626                 /* [HGM] we switched variant. Translate boards if needed. */
3627                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3628                 continue;
3629             }
3630
3631             if (looking_at(buf, &i, "Move  ")) {
3632                 /* Beginning of a move list */
3633                 switch (ics_getting_history) {
3634                   case H_FALSE:
3635                     /* Normally should not happen */
3636                     /* Maybe user hit reset while we were parsing */
3637                     break;
3638                   case H_REQUESTED:
3639                     /* Happens if we are ignoring a move list that is not
3640                      * the one we just requested.  Common if the user
3641                      * tries to observe two games without turning off
3642                      * getMoveList */
3643                     break;
3644                   case H_GETTING_MOVES:
3645                     /* Should not happen */
3646                     DisplayError(_("Error gathering move list: nested"), 0);
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649                   case H_GOT_REQ_HEADER:
3650                     ics_getting_history = H_GETTING_MOVES;
3651                     started = STARTED_MOVES;
3652                     parse_pos = 0;
3653                     if (oldi > next_out) {
3654                         SendToPlayer(&buf[next_out], oldi - next_out);
3655                     }
3656                     break;
3657                   case H_GOT_UNREQ_HEADER:
3658                     ics_getting_history = H_GETTING_MOVES;
3659                     started = STARTED_MOVES_NOHIDE;
3660                     parse_pos = 0;
3661                     break;
3662                   case H_GOT_UNWANTED_HEADER:
3663                     ics_getting_history = H_FALSE;
3664                     break;
3665                 }
3666                 continue;
3667             }
3668
3669             if (looking_at(buf, &i, "% ") ||
3670                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3671                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3672                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3673                     soughtPending = FALSE;
3674                     seekGraphUp = TRUE;
3675                     DrawSeekGraph();
3676                 }
3677                 if(suppressKibitz) next_out = i;
3678                 savingComment = FALSE;
3679                 suppressKibitz = 0;
3680                 switch (started) {
3681                   case STARTED_MOVES:
3682                   case STARTED_MOVES_NOHIDE:
3683                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3684                     parse[parse_pos + i - oldi] = NULLCHAR;
3685                     ParseGameHistory(parse);
3686 #if ZIPPY
3687                     if (appData.zippyPlay && first.initDone) {
3688                         FeedMovesToProgram(&first, forwardMostMove);
3689                         if (gameMode == IcsPlayingWhite) {
3690                             if (WhiteOnMove(forwardMostMove)) {
3691                                 if (first.sendTime) {
3692                                   if (first.useColors) {
3693                                     SendToProgram("black\n", &first);
3694                                   }
3695                                   SendTimeRemaining(&first, TRUE);
3696                                 }
3697                                 if (first.useColors) {
3698                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3699                                 }
3700                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3701                                 first.maybeThinking = TRUE;
3702                             } else {
3703                                 if (first.usePlayother) {
3704                                   if (first.sendTime) {
3705                                     SendTimeRemaining(&first, TRUE);
3706                                   }
3707                                   SendToProgram("playother\n", &first);
3708                                   firstMove = FALSE;
3709                                 } else {
3710                                   firstMove = TRUE;
3711                                 }
3712                             }
3713                         } else if (gameMode == IcsPlayingBlack) {
3714                             if (!WhiteOnMove(forwardMostMove)) {
3715                                 if (first.sendTime) {
3716                                   if (first.useColors) {
3717                                     SendToProgram("white\n", &first);
3718                                   }
3719                                   SendTimeRemaining(&first, FALSE);
3720                                 }
3721                                 if (first.useColors) {
3722                                   SendToProgram("black\n", &first);
3723                                 }
3724                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3725                                 first.maybeThinking = TRUE;
3726                             } else {
3727                                 if (first.usePlayother) {
3728                                   if (first.sendTime) {
3729                                     SendTimeRemaining(&first, FALSE);
3730                                   }
3731                                   SendToProgram("playother\n", &first);
3732                                   firstMove = FALSE;
3733                                 } else {
3734                                   firstMove = TRUE;
3735                                 }
3736                             }
3737                         }
3738                     }
3739 #endif
3740                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3741                         /* Moves came from oldmoves or moves command
3742                            while we weren't doing anything else.
3743                            */
3744                         currentMove = forwardMostMove;
3745                         ClearHighlights();/*!!could figure this out*/
3746                         flipView = appData.flipView;
3747                         DrawPosition(TRUE, boards[currentMove]);
3748                         DisplayBothClocks();
3749                         snprintf(str, MSG_SIZ, "%s %s %s",
3750                                 gameInfo.white, _("vs."),  gameInfo.black);
3751                         DisplayTitle(str);
3752                         gameMode = IcsIdle;
3753                     } else {
3754                         /* Moves were history of an active game */
3755                         if (gameInfo.resultDetails != NULL) {
3756                             free(gameInfo.resultDetails);
3757                             gameInfo.resultDetails = NULL;
3758                         }
3759                     }
3760                     HistorySet(parseList, backwardMostMove,
3761                                forwardMostMove, currentMove-1);
3762                     DisplayMove(currentMove - 1);
3763                     if (started == STARTED_MOVES) next_out = i;
3764                     started = STARTED_NONE;
3765                     ics_getting_history = H_FALSE;
3766                     break;
3767
3768                   case STARTED_OBSERVE:
3769                     started = STARTED_NONE;
3770                     SendToICS(ics_prefix);
3771                     SendToICS("refresh\n");
3772                     break;
3773
3774                   default:
3775                     break;
3776                 }
3777                 if(bookHit) { // [HGM] book: simulate book reply
3778                     static char bookMove[MSG_SIZ]; // a bit generous?
3779
3780                     programStats.nodes = programStats.depth = programStats.time =
3781                     programStats.score = programStats.got_only_move = 0;
3782                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3783
3784                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3785                     strcat(bookMove, bookHit);
3786                     HandleMachineMove(bookMove, &first);
3787                 }
3788                 continue;
3789             }
3790
3791             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3792                  started == STARTED_HOLDINGS ||
3793                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3794                 /* Accumulate characters in move list or board */
3795                 parse[parse_pos++] = buf[i];
3796             }
3797
3798             /* Start of game messages.  Mostly we detect start of game
3799                when the first board image arrives.  On some versions
3800                of the ICS, though, we need to do a "refresh" after starting
3801                to observe in order to get the current board right away. */
3802             if (looking_at(buf, &i, "Adding game * to observation list")) {
3803                 started = STARTED_OBSERVE;
3804                 continue;
3805             }
3806
3807             /* Handle auto-observe */
3808             if (appData.autoObserve &&
3809                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3810                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3811                 char *player;
3812                 /* Choose the player that was highlighted, if any. */
3813                 if (star_match[0][0] == '\033' ||
3814                     star_match[1][0] != '\033') {
3815                     player = star_match[0];
3816                 } else {
3817                     player = star_match[2];
3818                 }
3819                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3820                         ics_prefix, StripHighlightAndTitle(player));
3821                 SendToICS(str);
3822
3823                 /* Save ratings from notify string */
3824                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3825                 player1Rating = string_to_rating(star_match[1]);
3826                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3827                 player2Rating = string_to_rating(star_match[3]);
3828
3829                 if (appData.debugMode)
3830                   fprintf(debugFP,
3831                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3832                           player1Name, player1Rating,
3833                           player2Name, player2Rating);
3834
3835                 continue;
3836             }
3837
3838             /* Deal with automatic examine mode after a game,
3839                and with IcsObserving -> IcsExamining transition */
3840             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3841                 looking_at(buf, &i, "has made you an examiner of game *")) {
3842
3843                 int gamenum = atoi(star_match[0]);
3844                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3845                     gamenum == ics_gamenum) {
3846                     /* We were already playing or observing this game;
3847                        no need to refetch history */
3848                     gameMode = IcsExamining;
3849                     if (pausing) {
3850                         pauseExamForwardMostMove = forwardMostMove;
3851                     } else if (currentMove < forwardMostMove) {
3852                         ForwardInner(forwardMostMove);
3853                     }
3854                 } else {
3855                     /* I don't think this case really can happen */
3856                     SendToICS(ics_prefix);
3857                     SendToICS("refresh\n");
3858                 }
3859                 continue;
3860             }
3861
3862             /* Error messages */
3863 //          if (ics_user_moved) {
3864             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3865                 if (looking_at(buf, &i, "Illegal move") ||
3866                     looking_at(buf, &i, "Not a legal move") ||
3867                     looking_at(buf, &i, "Your king is in check") ||
3868                     looking_at(buf, &i, "It isn't your turn") ||
3869                     looking_at(buf, &i, "It is not your move")) {
3870                     /* Illegal move */
3871                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3872                         currentMove = forwardMostMove-1;
3873                         DisplayMove(currentMove - 1); /* before DMError */
3874                         DrawPosition(FALSE, boards[currentMove]);
3875                         SwitchClocks(forwardMostMove-1); // [HGM] race
3876                         DisplayBothClocks();
3877                     }
3878                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3879                     ics_user_moved = 0;
3880                     continue;
3881                 }
3882             }
3883
3884             if (looking_at(buf, &i, "still have time") ||
3885                 looking_at(buf, &i, "not out of time") ||
3886                 looking_at(buf, &i, "either player is out of time") ||
3887                 looking_at(buf, &i, "has timeseal; checking")) {
3888                 /* We must have called his flag a little too soon */
3889                 whiteFlag = blackFlag = FALSE;
3890                 continue;
3891             }
3892
3893             if (looking_at(buf, &i, "added * seconds to") ||
3894                 looking_at(buf, &i, "seconds were added to")) {
3895                 /* Update the clocks */
3896                 SendToICS(ics_prefix);
3897                 SendToICS("refresh\n");
3898                 continue;
3899             }
3900
3901             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3902                 ics_clock_paused = TRUE;
3903                 StopClocks();
3904                 continue;
3905             }
3906
3907             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3908                 ics_clock_paused = FALSE;
3909                 StartClocks();
3910                 continue;
3911             }
3912
3913             /* Grab player ratings from the Creating: message.
3914                Note we have to check for the special case when
3915                the ICS inserts things like [white] or [black]. */
3916             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3917                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3918                 /* star_matches:
3919                    0    player 1 name (not necessarily white)
3920                    1    player 1 rating
3921                    2    empty, white, or black (IGNORED)
3922                    3    player 2 name (not necessarily black)
3923                    4    player 2 rating
3924
3925                    The names/ratings are sorted out when the game
3926                    actually starts (below).
3927                 */
3928                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3929                 player1Rating = string_to_rating(star_match[1]);
3930                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3931                 player2Rating = string_to_rating(star_match[4]);
3932
3933                 if (appData.debugMode)
3934                   fprintf(debugFP,
3935                           "Ratings from 'Creating:' %s %d, %s %d\n",
3936                           player1Name, player1Rating,
3937                           player2Name, player2Rating);
3938
3939                 continue;
3940             }
3941
3942             /* Improved generic start/end-of-game messages */
3943             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3944                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3945                 /* If tkind == 0: */
3946                 /* star_match[0] is the game number */
3947                 /*           [1] is the white player's name */
3948                 /*           [2] is the black player's name */
3949                 /* For end-of-game: */
3950                 /*           [3] is the reason for the game end */
3951                 /*           [4] is a PGN end game-token, preceded by " " */
3952                 /* For start-of-game: */
3953                 /*           [3] begins with "Creating" or "Continuing" */
3954                 /*           [4] is " *" or empty (don't care). */
3955                 int gamenum = atoi(star_match[0]);
3956                 char *whitename, *blackname, *why, *endtoken;
3957                 ChessMove endtype = EndOfFile;
3958
3959                 if (tkind == 0) {
3960                   whitename = star_match[1];
3961                   blackname = star_match[2];
3962                   why = star_match[3];
3963                   endtoken = star_match[4];
3964                 } else {
3965                   whitename = star_match[1];
3966                   blackname = star_match[3];
3967                   why = star_match[5];
3968                   endtoken = star_match[6];
3969                 }
3970
3971                 /* Game start messages */
3972                 if (strncmp(why, "Creating ", 9) == 0 ||
3973                     strncmp(why, "Continuing ", 11) == 0) {
3974                     gs_gamenum = gamenum;
3975                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3976                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3977                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3978 #if ZIPPY
3979                     if (appData.zippyPlay) {
3980                         ZippyGameStart(whitename, blackname);
3981                     }
3982 #endif /*ZIPPY*/
3983                     partnerBoardValid = FALSE; // [HGM] bughouse
3984                     continue;
3985                 }
3986
3987                 /* Game end messages */
3988                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3989                     ics_gamenum != gamenum) {
3990                     continue;
3991                 }
3992                 while (endtoken[0] == ' ') endtoken++;
3993                 switch (endtoken[0]) {
3994                   case '*':
3995                   default:
3996                     endtype = GameUnfinished;
3997                     break;
3998                   case '0':
3999                     endtype = BlackWins;
4000                     break;
4001                   case '1':
4002                     if (endtoken[1] == '/')
4003                       endtype = GameIsDrawn;
4004                     else
4005                       endtype = WhiteWins;
4006                     break;
4007                 }
4008                 GameEnds(endtype, why, GE_ICS);
4009 #if ZIPPY
4010                 if (appData.zippyPlay && first.initDone) {
4011                     ZippyGameEnd(endtype, why);
4012                     if (first.pr == NoProc) {
4013                       /* Start the next process early so that we'll
4014                          be ready for the next challenge */
4015                       StartChessProgram(&first);
4016                     }
4017                     /* Send "new" early, in case this command takes
4018                        a long time to finish, so that we'll be ready
4019                        for the next challenge. */
4020                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4021                     Reset(TRUE, TRUE);
4022                 }
4023 #endif /*ZIPPY*/
4024                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4025                 continue;
4026             }
4027
4028             if (looking_at(buf, &i, "Removing game * from observation") ||
4029                 looking_at(buf, &i, "no longer observing game *") ||
4030                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4031                 if (gameMode == IcsObserving &&
4032                     atoi(star_match[0]) == ics_gamenum)
4033                   {
4034                       /* icsEngineAnalyze */
4035                       if (appData.icsEngineAnalyze) {
4036                             ExitAnalyzeMode();
4037                             ModeHighlight();
4038                       }
4039                       StopClocks();
4040                       gameMode = IcsIdle;
4041                       ics_gamenum = -1;
4042                       ics_user_moved = FALSE;
4043                   }
4044                 continue;
4045             }
4046
4047             if (looking_at(buf, &i, "no longer examining game *")) {
4048                 if (gameMode == IcsExamining &&
4049                     atoi(star_match[0]) == ics_gamenum)
4050                   {
4051                       gameMode = IcsIdle;
4052                       ics_gamenum = -1;
4053                       ics_user_moved = FALSE;
4054                   }
4055                 continue;
4056             }
4057
4058             /* Advance leftover_start past any newlines we find,
4059                so only partial lines can get reparsed */
4060             if (looking_at(buf, &i, "\n")) {
4061                 prevColor = curColor;
4062                 if (curColor != ColorNormal) {
4063                     if (oldi > next_out) {
4064                         SendToPlayer(&buf[next_out], oldi - next_out);
4065                         next_out = oldi;
4066                     }
4067                     Colorize(ColorNormal, FALSE);
4068                     curColor = ColorNormal;
4069                 }
4070                 if (started == STARTED_BOARD) {
4071                     started = STARTED_NONE;
4072                     parse[parse_pos] = NULLCHAR;
4073                     ParseBoard12(parse);
4074                     ics_user_moved = 0;
4075
4076                     /* Send premove here */
4077                     if (appData.premove) {
4078                       char str[MSG_SIZ];
4079                       if (currentMove == 0 &&
4080                           gameMode == IcsPlayingWhite &&
4081                           appData.premoveWhite) {
4082                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4083                         if (appData.debugMode)
4084                           fprintf(debugFP, "Sending premove:\n");
4085                         SendToICS(str);
4086                       } else if (currentMove == 1 &&
4087                                  gameMode == IcsPlayingBlack &&
4088                                  appData.premoveBlack) {
4089                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4090                         if (appData.debugMode)
4091                           fprintf(debugFP, "Sending premove:\n");
4092                         SendToICS(str);
4093                       } else if (gotPremove) {
4094                         gotPremove = 0;
4095                         ClearPremoveHighlights();
4096                         if (appData.debugMode)
4097                           fprintf(debugFP, "Sending premove:\n");
4098                           UserMoveEvent(premoveFromX, premoveFromY,
4099                                         premoveToX, premoveToY,
4100                                         premovePromoChar);
4101                       }
4102                     }
4103
4104                     /* Usually suppress following prompt */
4105                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4106                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4107                         if (looking_at(buf, &i, "*% ")) {
4108                             savingComment = FALSE;
4109                             suppressKibitz = 0;
4110                         }
4111                     }
4112                     next_out = i;
4113                 } else if (started == STARTED_HOLDINGS) {
4114                     int gamenum;
4115                     char new_piece[MSG_SIZ];
4116                     started = STARTED_NONE;
4117                     parse[parse_pos] = NULLCHAR;
4118                     if (appData.debugMode)
4119                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4120                                                         parse, currentMove);
4121                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4122                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4123                         if (gameInfo.variant == VariantNormal) {
4124                           /* [HGM] We seem to switch variant during a game!
4125                            * Presumably no holdings were displayed, so we have
4126                            * to move the position two files to the right to
4127                            * create room for them!
4128                            */
4129                           VariantClass newVariant;
4130                           switch(gameInfo.boardWidth) { // base guess on board width
4131                                 case 9:  newVariant = VariantShogi; break;
4132                                 case 10: newVariant = VariantGreat; break;
4133                                 default: newVariant = VariantCrazyhouse; break;
4134                           }
4135                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4136                           /* Get a move list just to see the header, which
4137                              will tell us whether this is really bug or zh */
4138                           if (ics_getting_history == H_FALSE) {
4139                             ics_getting_history = H_REQUESTED;
4140                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4141                             SendToICS(str);
4142                           }
4143                         }
4144                         new_piece[0] = NULLCHAR;
4145                         sscanf(parse, "game %d white [%s black [%s <- %s",
4146                                &gamenum, white_holding, black_holding,
4147                                new_piece);
4148                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4149                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4150                         /* [HGM] copy holdings to board holdings area */
4151                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4152                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4153                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4154 #if ZIPPY
4155                         if (appData.zippyPlay && first.initDone) {
4156                             ZippyHoldings(white_holding, black_holding,
4157                                           new_piece);
4158                         }
4159 #endif /*ZIPPY*/
4160                         if (tinyLayout || smallLayout) {
4161                             char wh[16], bh[16];
4162                             PackHolding(wh, white_holding);
4163                             PackHolding(bh, black_holding);
4164                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4165                                     gameInfo.white, gameInfo.black);
4166                         } else {
4167                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4168                                     gameInfo.white, white_holding, _("vs."),
4169                                     gameInfo.black, black_holding);
4170                         }
4171                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4172                         DrawPosition(FALSE, boards[currentMove]);
4173                         DisplayTitle(str);
4174                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4175                         sscanf(parse, "game %d white [%s black [%s <- %s",
4176                                &gamenum, white_holding, black_holding,
4177                                new_piece);
4178                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4179                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4180                         /* [HGM] copy holdings to partner-board holdings area */
4181                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4182                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4183                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4184                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4185                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4186                       }
4187                     }
4188                     /* Suppress following prompt */
4189                     if (looking_at(buf, &i, "*% ")) {
4190                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4191                         savingComment = FALSE;
4192                         suppressKibitz = 0;
4193                     }
4194                     next_out = i;
4195                 }
4196                 continue;
4197             }
4198
4199             i++;                /* skip unparsed character and loop back */
4200         }
4201
4202         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4203 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4204 //          SendToPlayer(&buf[next_out], i - next_out);
4205             started != STARTED_HOLDINGS && leftover_start > next_out) {
4206             SendToPlayer(&buf[next_out], leftover_start - next_out);
4207             next_out = i;
4208         }
4209
4210         leftover_len = buf_len - leftover_start;
4211         /* if buffer ends with something we couldn't parse,
4212            reparse it after appending the next read */
4213
4214     } else if (count == 0) {
4215         RemoveInputSource(isr);
4216         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4217     } else {
4218         DisplayFatalError(_("Error reading from ICS"), error, 1);
4219     }
4220 }
4221
4222
4223 /* Board style 12 looks like this:
4224
4225    <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
4226
4227  * The "<12> " is stripped before it gets to this routine.  The two
4228  * trailing 0's (flip state and clock ticking) are later addition, and
4229  * some chess servers may not have them, or may have only the first.
4230  * Additional trailing fields may be added in the future.
4231  */
4232
4233 #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"
4234
4235 #define RELATION_OBSERVING_PLAYED    0
4236 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4237 #define RELATION_PLAYING_MYMOVE      1
4238 #define RELATION_PLAYING_NOTMYMOVE  -1
4239 #define RELATION_EXAMINING           2
4240 #define RELATION_ISOLATED_BOARD     -3
4241 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4242
4243 void
4244 ParseBoard12 (char *string)
4245 {
4246 #if ZIPPY
4247     int i, takeback;
4248     char *bookHit = NULL; // [HGM] book
4249 #endif
4250     GameMode newGameMode;
4251     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4252     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4253     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4254     char to_play, board_chars[200];
4255     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4256     char black[32], white[32];
4257     Board board;
4258     int prevMove = currentMove;
4259     int ticking = 2;
4260     ChessMove moveType;
4261     int fromX, fromY, toX, toY;
4262     char promoChar;
4263     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4264     Boolean weird = FALSE, reqFlag = FALSE;
4265
4266     fromX = fromY = toX = toY = -1;
4267
4268     newGame = FALSE;
4269
4270     if (appData.debugMode)
4271       fprintf(debugFP, "Parsing board: %s\n", string);
4272
4273     move_str[0] = NULLCHAR;
4274     elapsed_time[0] = NULLCHAR;
4275     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4276         int  i = 0, j;
4277         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4278             if(string[i] == ' ') { ranks++; files = 0; }
4279             else files++;
4280             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4281             i++;
4282         }
4283         for(j = 0; j <i; j++) board_chars[j] = string[j];
4284         board_chars[i] = '\0';
4285         string += i + 1;
4286     }
4287     n = sscanf(string, PATTERN, &to_play, &double_push,
4288                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4289                &gamenum, white, black, &relation, &basetime, &increment,
4290                &white_stren, &black_stren, &white_time, &black_time,
4291                &moveNum, str, elapsed_time, move_str, &ics_flip,
4292                &ticking);
4293
4294     if (n < 21) {
4295         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4296         DisplayError(str, 0);
4297         return;
4298     }
4299
4300     /* Convert the move number to internal form */
4301     moveNum = (moveNum - 1) * 2;
4302     if (to_play == 'B') moveNum++;
4303     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4304       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4305                         0, 1);
4306       return;
4307     }
4308
4309     switch (relation) {
4310       case RELATION_OBSERVING_PLAYED:
4311       case RELATION_OBSERVING_STATIC:
4312         if (gamenum == -1) {
4313             /* Old ICC buglet */
4314             relation = RELATION_OBSERVING_STATIC;
4315         }
4316         newGameMode = IcsObserving;
4317         break;
4318       case RELATION_PLAYING_MYMOVE:
4319       case RELATION_PLAYING_NOTMYMOVE:
4320         newGameMode =
4321           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4322             IcsPlayingWhite : IcsPlayingBlack;
4323         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4324         break;
4325       case RELATION_EXAMINING:
4326         newGameMode = IcsExamining;
4327         break;
4328       case RELATION_ISOLATED_BOARD:
4329       default:
4330         /* Just display this board.  If user was doing something else,
4331            we will forget about it until the next board comes. */
4332         newGameMode = IcsIdle;
4333         break;
4334       case RELATION_STARTING_POSITION:
4335         newGameMode = gameMode;
4336         break;
4337     }
4338
4339     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4340         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4341          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4342       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4343       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4344       static int lastBgGame = -1;
4345       char *toSqr;
4346       for (k = 0; k < ranks; k++) {
4347         for (j = 0; j < files; j++)
4348           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4349         if(gameInfo.holdingsWidth > 1) {
4350              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4351              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4352         }
4353       }
4354       CopyBoard(partnerBoard, board);
4355       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4356         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4357         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4358       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4359       if(toSqr = strchr(str, '-')) {
4360         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4361         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4362       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4363       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4364       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4365       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4366       if(twoBoards) {
4367           DisplayWhiteClock(white_time*fac, to_play == 'W');
4368           DisplayBlackClock(black_time*fac, to_play != 'W');
4369           activePartner = to_play;
4370           if(gamenum != lastBgGame) {
4371               char buf[MSG_SIZ];
4372               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4373               DisplayTitle(buf);
4374           }
4375           lastBgGame = gamenum;
4376           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4377                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4378       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4379                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4380       if(!twoBoards) DisplayMessage(partnerStatus, "");
4381         partnerBoardValid = TRUE;
4382       return;
4383     }
4384
4385     if(appData.dualBoard && appData.bgObserve) {
4386         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4387             SendToICS(ics_prefix), SendToICS("pobserve\n");
4388         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4389             char buf[MSG_SIZ];
4390             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4391             SendToICS(buf);
4392         }
4393     }
4394
4395     /* Modify behavior for initial board display on move listing
4396        of wild games.
4397        */
4398     switch (ics_getting_history) {
4399       case H_FALSE:
4400       case H_REQUESTED:
4401         break;
4402       case H_GOT_REQ_HEADER:
4403       case H_GOT_UNREQ_HEADER:
4404         /* This is the initial position of the current game */
4405         gamenum = ics_gamenum;
4406         moveNum = 0;            /* old ICS bug workaround */
4407         if (to_play == 'B') {
4408           startedFromSetupPosition = TRUE;
4409           blackPlaysFirst = TRUE;
4410           moveNum = 1;
4411           if (forwardMostMove == 0) forwardMostMove = 1;
4412           if (backwardMostMove == 0) backwardMostMove = 1;
4413           if (currentMove == 0) currentMove = 1;
4414         }
4415         newGameMode = gameMode;
4416         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4417         break;
4418       case H_GOT_UNWANTED_HEADER:
4419         /* This is an initial board that we don't want */
4420         return;
4421       case H_GETTING_MOVES:
4422         /* Should not happen */
4423         DisplayError(_("Error gathering move list: extra board"), 0);
4424         ics_getting_history = H_FALSE;
4425         return;
4426     }
4427
4428    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4429                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4430                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4431      /* [HGM] We seem to have switched variant unexpectedly
4432       * Try to guess new variant from board size
4433       */
4434           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4435           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4436           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4437           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4438           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4439           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4440           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4441           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4442           /* Get a move list just to see the header, which
4443              will tell us whether this is really bug or zh */
4444           if (ics_getting_history == H_FALSE) {
4445             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4446             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4447             SendToICS(str);
4448           }
4449     }
4450
4451     /* Take action if this is the first board of a new game, or of a
4452        different game than is currently being displayed.  */
4453     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4454         relation == RELATION_ISOLATED_BOARD) {
4455
4456         /* Forget the old game and get the history (if any) of the new one */
4457         if (gameMode != BeginningOfGame) {
4458           Reset(TRUE, TRUE);
4459         }
4460         newGame = TRUE;
4461         if (appData.autoRaiseBoard) BoardToTop();
4462         prevMove = -3;
4463         if (gamenum == -1) {
4464             newGameMode = IcsIdle;
4465         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4466                    appData.getMoveList && !reqFlag) {
4467             /* Need to get game history */
4468             ics_getting_history = H_REQUESTED;
4469             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4470             SendToICS(str);
4471         }
4472
4473         /* Initially flip the board to have black on the bottom if playing
4474            black or if the ICS flip flag is set, but let the user change
4475            it with the Flip View button. */
4476         flipView = appData.autoFlipView ?
4477           (newGameMode == IcsPlayingBlack) || ics_flip :
4478           appData.flipView;
4479
4480         /* Done with values from previous mode; copy in new ones */
4481         gameMode = newGameMode;
4482         ModeHighlight();
4483         ics_gamenum = gamenum;
4484         if (gamenum == gs_gamenum) {
4485             int klen = strlen(gs_kind);
4486             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4487             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4488             gameInfo.event = StrSave(str);
4489         } else {
4490             gameInfo.event = StrSave("ICS game");
4491         }
4492         gameInfo.site = StrSave(appData.icsHost);
4493         gameInfo.date = PGNDate();
4494         gameInfo.round = StrSave("-");
4495         gameInfo.white = StrSave(white);
4496         gameInfo.black = StrSave(black);
4497         timeControl = basetime * 60 * 1000;
4498         timeControl_2 = 0;
4499         timeIncrement = increment * 1000;
4500         movesPerSession = 0;
4501         gameInfo.timeControl = TimeControlTagValue();
4502         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4503   if (appData.debugMode) {
4504     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4505     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4506     setbuf(debugFP, NULL);
4507   }
4508
4509         gameInfo.outOfBook = NULL;
4510
4511         /* Do we have the ratings? */
4512         if (strcmp(player1Name, white) == 0 &&
4513             strcmp(player2Name, black) == 0) {
4514             if (appData.debugMode)
4515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4516                       player1Rating, player2Rating);
4517             gameInfo.whiteRating = player1Rating;
4518             gameInfo.blackRating = player2Rating;
4519         } else if (strcmp(player2Name, white) == 0 &&
4520                    strcmp(player1Name, black) == 0) {
4521             if (appData.debugMode)
4522               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4523                       player2Rating, player1Rating);
4524             gameInfo.whiteRating = player2Rating;
4525             gameInfo.blackRating = player1Rating;
4526         }
4527         player1Name[0] = player2Name[0] = NULLCHAR;
4528
4529         /* Silence shouts if requested */
4530         if (appData.quietPlay &&
4531             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4532             SendToICS(ics_prefix);
4533             SendToICS("set shout 0\n");
4534         }
4535     }
4536
4537     /* Deal with midgame name changes */
4538     if (!newGame) {
4539         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4540             if (gameInfo.white) free(gameInfo.white);
4541             gameInfo.white = StrSave(white);
4542         }
4543         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4544             if (gameInfo.black) free(gameInfo.black);
4545             gameInfo.black = StrSave(black);
4546         }
4547     }
4548
4549     /* Throw away game result if anything actually changes in examine mode */
4550     if (gameMode == IcsExamining && !newGame) {
4551         gameInfo.result = GameUnfinished;
4552         if (gameInfo.resultDetails != NULL) {
4553             free(gameInfo.resultDetails);
4554             gameInfo.resultDetails = NULL;
4555         }
4556     }
4557
4558     /* In pausing && IcsExamining mode, we ignore boards coming
4559        in if they are in a different variation than we are. */
4560     if (pauseExamInvalid) return;
4561     if (pausing && gameMode == IcsExamining) {
4562         if (moveNum <= pauseExamForwardMostMove) {
4563             pauseExamInvalid = TRUE;
4564             forwardMostMove = pauseExamForwardMostMove;
4565             return;
4566         }
4567     }
4568
4569   if (appData.debugMode) {
4570     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4571   }
4572     /* Parse the board */
4573     for (k = 0; k < ranks; k++) {
4574       for (j = 0; j < files; j++)
4575         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4576       if(gameInfo.holdingsWidth > 1) {
4577            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4578            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4579       }
4580     }
4581     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4582       board[5][BOARD_RGHT+1] = WhiteAngel;
4583       board[6][BOARD_RGHT+1] = WhiteMarshall;
4584       board[1][0] = BlackMarshall;
4585       board[2][0] = BlackAngel;
4586       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4587     }
4588     CopyBoard(boards[moveNum], board);
4589     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4590     if (moveNum == 0) {
4591         startedFromSetupPosition =
4592           !CompareBoards(board, initialPosition);
4593         if(startedFromSetupPosition)
4594             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4595     }
4596
4597     /* [HGM] Set castling rights. Take the outermost Rooks,
4598        to make it also work for FRC opening positions. Note that board12
4599        is really defective for later FRC positions, as it has no way to
4600        indicate which Rook can castle if they are on the same side of King.
4601        For the initial position we grant rights to the outermost Rooks,
4602        and remember thos rights, and we then copy them on positions
4603        later in an FRC game. This means WB might not recognize castlings with
4604        Rooks that have moved back to their original position as illegal,
4605        but in ICS mode that is not its job anyway.
4606     */
4607     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4608     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4609
4610         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4611             if(board[0][i] == WhiteRook) j = i;
4612         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4613         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4614             if(board[0][i] == WhiteRook) j = i;
4615         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4616         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4617             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4618         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4619         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4620             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4621         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4622
4623         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4624         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4625         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4626             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4627         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4628             if(board[BOARD_HEIGHT-1][k] == bKing)
4629                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4630         if(gameInfo.variant == VariantTwoKings) {
4631             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4632             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4633             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4634         }
4635     } else { int r;
4636         r = boards[moveNum][CASTLING][0] = initialRights[0];
4637         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4638         r = boards[moveNum][CASTLING][1] = initialRights[1];
4639         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4640         r = boards[moveNum][CASTLING][3] = initialRights[3];
4641         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4642         r = boards[moveNum][CASTLING][4] = initialRights[4];
4643         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4644         /* wildcastle kludge: always assume King has rights */
4645         r = boards[moveNum][CASTLING][2] = initialRights[2];
4646         r = boards[moveNum][CASTLING][5] = initialRights[5];
4647     }
4648     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4649     boards[moveNum][EP_STATUS] = EP_NONE;
4650     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4651     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4652     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4653
4654
4655     if (ics_getting_history == H_GOT_REQ_HEADER ||
4656         ics_getting_history == H_GOT_UNREQ_HEADER) {
4657         /* This was an initial position from a move list, not
4658            the current position */
4659         return;
4660     }
4661
4662     /* Update currentMove and known move number limits */
4663     newMove = newGame || moveNum > forwardMostMove;
4664
4665     if (newGame) {
4666         forwardMostMove = backwardMostMove = currentMove = moveNum;
4667         if (gameMode == IcsExamining && moveNum == 0) {
4668           /* Workaround for ICS limitation: we are not told the wild
4669              type when starting to examine a game.  But if we ask for
4670              the move list, the move list header will tell us */
4671             ics_getting_history = H_REQUESTED;
4672             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4673             SendToICS(str);
4674         }
4675     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4676                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4677 #if ZIPPY
4678         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4679         /* [HGM] applied this also to an engine that is silently watching        */
4680         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4681             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4682             gameInfo.variant == currentlyInitializedVariant) {
4683           takeback = forwardMostMove - moveNum;
4684           for (i = 0; i < takeback; i++) {
4685             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4686             SendToProgram("undo\n", &first);
4687           }
4688         }
4689 #endif
4690
4691         forwardMostMove = moveNum;
4692         if (!pausing || currentMove > forwardMostMove)
4693           currentMove = forwardMostMove;
4694     } else {
4695         /* New part of history that is not contiguous with old part */
4696         if (pausing && gameMode == IcsExamining) {
4697             pauseExamInvalid = TRUE;
4698             forwardMostMove = pauseExamForwardMostMove;
4699             return;
4700         }
4701         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4702 #if ZIPPY
4703             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4704                 // [HGM] when we will receive the move list we now request, it will be
4705                 // fed to the engine from the first move on. So if the engine is not
4706                 // in the initial position now, bring it there.
4707                 InitChessProgram(&first, 0);
4708             }
4709 #endif
4710             ics_getting_history = H_REQUESTED;
4711             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4712             SendToICS(str);
4713         }
4714         forwardMostMove = backwardMostMove = currentMove = moveNum;
4715     }
4716
4717     /* Update the clocks */
4718     if (strchr(elapsed_time, '.')) {
4719       /* Time is in ms */
4720       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4721       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4722     } else {
4723       /* Time is in seconds */
4724       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4725       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4726     }
4727
4728
4729 #if ZIPPY
4730     if (appData.zippyPlay && newGame &&
4731         gameMode != IcsObserving && gameMode != IcsIdle &&
4732         gameMode != IcsExamining)
4733       ZippyFirstBoard(moveNum, basetime, increment);
4734 #endif
4735
4736     /* Put the move on the move list, first converting
4737        to canonical algebraic form. */
4738     if (moveNum > 0) {
4739   if (appData.debugMode) {
4740     int f = forwardMostMove;
4741     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4742             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4743             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4744     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4745     fprintf(debugFP, "moveNum = %d\n", moveNum);
4746     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4747     setbuf(debugFP, NULL);
4748   }
4749         if (moveNum <= backwardMostMove) {
4750             /* We don't know what the board looked like before
4751                this move.  Punt. */
4752           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4753             strcat(parseList[moveNum - 1], " ");
4754             strcat(parseList[moveNum - 1], elapsed_time);
4755             moveList[moveNum - 1][0] = NULLCHAR;
4756         } else if (strcmp(move_str, "none") == 0) {
4757             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4758             /* Again, we don't know what the board looked like;
4759                this is really the start of the game. */
4760             parseList[moveNum - 1][0] = NULLCHAR;
4761             moveList[moveNum - 1][0] = NULLCHAR;
4762             backwardMostMove = moveNum;
4763             startedFromSetupPosition = TRUE;
4764             fromX = fromY = toX = toY = -1;
4765         } else {
4766           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4767           //                 So we parse the long-algebraic move string in stead of the SAN move
4768           int valid; char buf[MSG_SIZ], *prom;
4769
4770           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4771                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4772           // str looks something like "Q/a1-a2"; kill the slash
4773           if(str[1] == '/')
4774             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4775           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4776           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4777                 strcat(buf, prom); // long move lacks promo specification!
4778           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4779                 if(appData.debugMode)
4780                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4781                 safeStrCpy(move_str, buf, MSG_SIZ);
4782           }
4783           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4784                                 &fromX, &fromY, &toX, &toY, &promoChar)
4785                || ParseOneMove(buf, moveNum - 1, &moveType,
4786                                 &fromX, &fromY, &toX, &toY, &promoChar);
4787           // end of long SAN patch
4788           if (valid) {
4789             (void) CoordsToAlgebraic(boards[moveNum - 1],
4790                                      PosFlags(moveNum - 1),
4791                                      fromY, fromX, toY, toX, promoChar,
4792                                      parseList[moveNum-1]);
4793             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4794               case MT_NONE:
4795               case MT_STALEMATE:
4796               default:
4797                 break;
4798               case MT_CHECK:
4799                 if(gameInfo.variant != VariantShogi)
4800                     strcat(parseList[moveNum - 1], "+");
4801                 break;
4802               case MT_CHECKMATE:
4803               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4804                 strcat(parseList[moveNum - 1], "#");
4805                 break;
4806             }
4807             strcat(parseList[moveNum - 1], " ");
4808             strcat(parseList[moveNum - 1], elapsed_time);
4809             /* currentMoveString is set as a side-effect of ParseOneMove */
4810             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4811             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4812             strcat(moveList[moveNum - 1], "\n");
4813
4814             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4815                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4816               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4817                 ChessSquare old, new = boards[moveNum][k][j];
4818                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4819                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4820                   if(old == new) continue;
4821                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4822                   else if(new == WhiteWazir || new == BlackWazir) {
4823                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4824                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4825                       else boards[moveNum][k][j] = old; // preserve type of Gold
4826                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4827                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4828               }
4829           } else {
4830             /* Move from ICS was illegal!?  Punt. */
4831             if (appData.debugMode) {
4832               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4833               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4834             }
4835             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4836             strcat(parseList[moveNum - 1], " ");
4837             strcat(parseList[moveNum - 1], elapsed_time);
4838             moveList[moveNum - 1][0] = NULLCHAR;
4839             fromX = fromY = toX = toY = -1;
4840           }
4841         }
4842   if (appData.debugMode) {
4843     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4844     setbuf(debugFP, NULL);
4845   }
4846
4847 #if ZIPPY
4848         /* Send move to chess program (BEFORE animating it). */
4849         if (appData.zippyPlay && !newGame && newMove &&
4850            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4851
4852             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4853                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4854                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4855                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4856                             move_str);
4857                     DisplayError(str, 0);
4858                 } else {
4859                     if (first.sendTime) {
4860                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4861                     }
4862                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4863                     if (firstMove && !bookHit) {
4864                         firstMove = FALSE;
4865                         if (first.useColors) {
4866                           SendToProgram(gameMode == IcsPlayingWhite ?
4867                                         "white\ngo\n" :
4868                                         "black\ngo\n", &first);
4869                         } else {
4870                           SendToProgram("go\n", &first);
4871                         }
4872                         first.maybeThinking = TRUE;
4873                     }
4874                 }
4875             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4876               if (moveList[moveNum - 1][0] == NULLCHAR) {
4877                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4878                 DisplayError(str, 0);
4879               } else {
4880                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4881                 SendMoveToProgram(moveNum - 1, &first);
4882               }
4883             }
4884         }
4885 #endif
4886     }
4887
4888     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4889         /* If move comes from a remote source, animate it.  If it
4890            isn't remote, it will have already been animated. */
4891         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4892             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4893         }
4894         if (!pausing && appData.highlightLastMove) {
4895             SetHighlights(fromX, fromY, toX, toY);
4896         }
4897     }
4898
4899     /* Start the clocks */
4900     whiteFlag = blackFlag = FALSE;
4901     appData.clockMode = !(basetime == 0 && increment == 0);
4902     if (ticking == 0) {
4903       ics_clock_paused = TRUE;
4904       StopClocks();
4905     } else if (ticking == 1) {
4906       ics_clock_paused = FALSE;
4907     }
4908     if (gameMode == IcsIdle ||
4909         relation == RELATION_OBSERVING_STATIC ||
4910         relation == RELATION_EXAMINING ||
4911         ics_clock_paused)
4912       DisplayBothClocks();
4913     else
4914       StartClocks();
4915
4916     /* Display opponents and material strengths */
4917     if (gameInfo.variant != VariantBughouse &&
4918         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4919         if (tinyLayout || smallLayout) {
4920             if(gameInfo.variant == VariantNormal)
4921               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4922                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4923                     basetime, increment);
4924             else
4925               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4926                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4927                     basetime, increment, (int) gameInfo.variant);
4928         } else {
4929             if(gameInfo.variant == VariantNormal)
4930               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4931                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4932                     basetime, increment);
4933             else
4934               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4935                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4936                     basetime, increment, VariantName(gameInfo.variant));
4937         }
4938         DisplayTitle(str);
4939   if (appData.debugMode) {
4940     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4941   }
4942     }
4943
4944
4945     /* Display the board */
4946     if (!pausing && !appData.noGUI) {
4947
4948       if (appData.premove)
4949           if (!gotPremove ||
4950              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4951              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4952               ClearPremoveHighlights();
4953
4954       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4955         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4956       DrawPosition(j, boards[currentMove]);
4957
4958       DisplayMove(moveNum - 1);
4959       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4960             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4961               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4962         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4963       }
4964     }
4965
4966     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4967 #if ZIPPY
4968     if(bookHit) { // [HGM] book: simulate book reply
4969         static char bookMove[MSG_SIZ]; // a bit generous?
4970
4971         programStats.nodes = programStats.depth = programStats.time =
4972         programStats.score = programStats.got_only_move = 0;
4973         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4974
4975         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4976         strcat(bookMove, bookHit);
4977         HandleMachineMove(bookMove, &first);
4978     }
4979 #endif
4980 }
4981
4982 void
4983 GetMoveListEvent ()
4984 {
4985     char buf[MSG_SIZ];
4986     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4987         ics_getting_history = H_REQUESTED;
4988         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4989         SendToICS(buf);
4990     }
4991 }
4992
4993 void
4994 SendToBoth (char *msg)
4995 {   // to make it easy to keep two engines in step in dual analysis
4996     SendToProgram(msg, &first);
4997     if(second.analyzing) SendToProgram(msg, &second);
4998 }
4999
5000 void
5001 AnalysisPeriodicEvent (int force)
5002 {
5003     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5004          && !force) || !appData.periodicUpdates)
5005       return;
5006
5007     /* Send . command to Crafty to collect stats */
5008     SendToBoth(".\n");
5009
5010     /* Don't send another until we get a response (this makes
5011        us stop sending to old Crafty's which don't understand
5012        the "." command (sending illegal cmds resets node count & time,
5013        which looks bad)) */
5014     programStats.ok_to_send = 0;
5015 }
5016
5017 void
5018 ics_update_width (int new_width)
5019 {
5020         ics_printf("set width %d\n", new_width);
5021 }
5022
5023 void
5024 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5025 {
5026     char buf[MSG_SIZ];
5027
5028     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5029         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5030             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5031             SendToProgram(buf, cps);
5032             return;
5033         }
5034         // null move in variant where engine does not understand it (for analysis purposes)
5035         SendBoard(cps, moveNum + 1); // send position after move in stead.
5036         return;
5037     }
5038     if (cps->useUsermove) {
5039       SendToProgram("usermove ", cps);
5040     }
5041     if (cps->useSAN) {
5042       char *space;
5043       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5044         int len = space - parseList[moveNum];
5045         memcpy(buf, parseList[moveNum], len);
5046         buf[len++] = '\n';
5047         buf[len] = NULLCHAR;
5048       } else {
5049         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5050       }
5051       SendToProgram(buf, cps);
5052     } else {
5053       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5054         AlphaRank(moveList[moveNum], 4);
5055         SendToProgram(moveList[moveNum], cps);
5056         AlphaRank(moveList[moveNum], 4); // and back
5057       } else
5058       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5059        * the engine. It would be nice to have a better way to identify castle
5060        * moves here. */
5061       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5062                                                                          && cps->useOOCastle) {
5063         int fromX = moveList[moveNum][0] - AAA;
5064         int fromY = moveList[moveNum][1] - ONE;
5065         int toX = moveList[moveNum][2] - AAA;
5066         int toY = moveList[moveNum][3] - ONE;
5067         if((boards[moveNum][fromY][fromX] == WhiteKing
5068             && boards[moveNum][toY][toX] == WhiteRook)
5069            || (boards[moveNum][fromY][fromX] == BlackKing
5070                && boards[moveNum][toY][toX] == BlackRook)) {
5071           if(toX > fromX) SendToProgram("O-O\n", cps);
5072           else SendToProgram("O-O-O\n", cps);
5073         }
5074         else SendToProgram(moveList[moveNum], cps);
5075       } else
5076       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5077           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5078                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5079                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5080                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5081           SendToProgram(buf, cps);
5082       } else
5083       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5084         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5085           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5086           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5087                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5088         } else
5089           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5090                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5091         SendToProgram(buf, cps);
5092       }
5093       else SendToProgram(moveList[moveNum], cps);
5094       /* End of additions by Tord */
5095     }
5096
5097     /* [HGM] setting up the opening has brought engine in force mode! */
5098     /*       Send 'go' if we are in a mode where machine should play. */
5099     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5100         (gameMode == TwoMachinesPlay   ||
5101 #if ZIPPY
5102          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5103 #endif
5104          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5105         SendToProgram("go\n", cps);
5106   if (appData.debugMode) {
5107     fprintf(debugFP, "(extra)\n");
5108   }
5109     }
5110     setboardSpoiledMachineBlack = 0;
5111 }
5112
5113 void
5114 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5115 {
5116     char user_move[MSG_SIZ];
5117     char suffix[4];
5118
5119     if(gameInfo.variant == VariantSChess && promoChar) {
5120         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5121         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5122     } else suffix[0] = NULLCHAR;
5123
5124     switch (moveType) {
5125       default:
5126         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5127                 (int)moveType, fromX, fromY, toX, toY);
5128         DisplayError(user_move + strlen("say "), 0);
5129         break;
5130       case WhiteKingSideCastle:
5131       case BlackKingSideCastle:
5132       case WhiteQueenSideCastleWild:
5133       case BlackQueenSideCastleWild:
5134       /* PUSH Fabien */
5135       case WhiteHSideCastleFR:
5136       case BlackHSideCastleFR:
5137       /* POP Fabien */
5138         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5139         break;
5140       case WhiteQueenSideCastle:
5141       case BlackQueenSideCastle:
5142       case WhiteKingSideCastleWild:
5143       case BlackKingSideCastleWild:
5144       /* PUSH Fabien */
5145       case WhiteASideCastleFR:
5146       case BlackASideCastleFR:
5147       /* POP Fabien */
5148         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5149         break;
5150       case WhiteNonPromotion:
5151       case BlackNonPromotion:
5152         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5153         break;
5154       case WhitePromotion:
5155       case BlackPromotion:
5156         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5157            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5158           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5159                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5160                 PieceToChar(WhiteFerz));
5161         else if(gameInfo.variant == VariantGreat)
5162           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5163                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5164                 PieceToChar(WhiteMan));
5165         else
5166           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5167                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5168                 promoChar);
5169         break;
5170       case WhiteDrop:
5171       case BlackDrop:
5172       drop:
5173         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5174                  ToUpper(PieceToChar((ChessSquare) fromX)),
5175                  AAA + toX, ONE + toY);
5176         break;
5177       case IllegalMove:  /* could be a variant we don't quite understand */
5178         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5179       case NormalMove:
5180       case WhiteCapturesEnPassant:
5181       case BlackCapturesEnPassant:
5182         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5183                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5184         break;
5185     }
5186     SendToICS(user_move);
5187     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5188         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5189 }
5190
5191 void
5192 UploadGameEvent ()
5193 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5194     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5195     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5196     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5197       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5198       return;
5199     }
5200     if(gameMode != IcsExamining) { // is this ever not the case?
5201         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5202
5203         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5204           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5205         } else { // on FICS we must first go to general examine mode
5206           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5207         }
5208         if(gameInfo.variant != VariantNormal) {
5209             // try figure out wild number, as xboard names are not always valid on ICS
5210             for(i=1; i<=36; i++) {
5211               snprintf(buf, MSG_SIZ, "wild/%d", i);
5212                 if(StringToVariant(buf) == gameInfo.variant) break;
5213             }
5214             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5215             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5216             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5217         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5218         SendToICS(ics_prefix);
5219         SendToICS(buf);
5220         if(startedFromSetupPosition || backwardMostMove != 0) {
5221           fen = PositionToFEN(backwardMostMove, NULL, 1);
5222           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5223             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5224             SendToICS(buf);
5225           } else { // FICS: everything has to set by separate bsetup commands
5226             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5227             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5228             SendToICS(buf);
5229             if(!WhiteOnMove(backwardMostMove)) {
5230                 SendToICS("bsetup tomove black\n");
5231             }
5232             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5233             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5234             SendToICS(buf);
5235             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5236             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5237             SendToICS(buf);
5238             i = boards[backwardMostMove][EP_STATUS];
5239             if(i >= 0) { // set e.p.
5240               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5241                 SendToICS(buf);
5242             }
5243             bsetup++;
5244           }
5245         }
5246       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5247             SendToICS("bsetup done\n"); // switch to normal examining.
5248     }
5249     for(i = backwardMostMove; i<last; i++) {
5250         char buf[20];
5251         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5252         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5253             int len = strlen(moveList[i]);
5254             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5255             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5256         }
5257         SendToICS(buf);
5258     }
5259     SendToICS(ics_prefix);
5260     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5261 }
5262
5263 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5264
5265 void
5266 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5267 {
5268     if (rf == DROP_RANK) {
5269       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5270       sprintf(move, "%c@%c%c\n",
5271                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5272     } else {
5273         if (promoChar == 'x' || promoChar == NULLCHAR) {
5274           sprintf(move, "%c%c%c%c\n",
5275                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5276           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5277         } else {
5278             sprintf(move, "%c%c%c%c%c\n",
5279                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5280         }
5281     }
5282 }
5283
5284 void
5285 ProcessICSInitScript (FILE *f)
5286 {
5287     char buf[MSG_SIZ];
5288
5289     while (fgets(buf, MSG_SIZ, f)) {
5290         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5291     }
5292
5293     fclose(f);
5294 }
5295
5296
5297 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5298 int dragging;
5299 static ClickType lastClickType;
5300
5301 int
5302 Partner (ChessSquare *p)
5303 { // change piece into promotion partner if one shogi-promotes to the other
5304   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5305   ChessSquare partner;
5306   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5307   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5308   *p = partner;
5309   return 1;
5310 }
5311
5312 void
5313 Sweep (int step)
5314 {
5315     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5316     static int toggleFlag;
5317     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5318     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5319     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5320     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5321     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5322     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5323     do {
5324         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5325         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5326         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5327         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5328         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5329         if(!step) step = -1;
5330     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5331             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion));
5332     if(toX >= 0) {
5333         int victim = boards[currentMove][toY][toX];
5334         boards[currentMove][toY][toX] = promoSweep;
5335         DrawPosition(FALSE, boards[currentMove]);
5336         boards[currentMove][toY][toX] = victim;
5337     } else
5338     ChangeDragPiece(promoSweep);
5339 }
5340
5341 int
5342 PromoScroll (int x, int y)
5343 {
5344   int step = 0;
5345
5346   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5347   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5348   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5349   if(!step) return FALSE;
5350   lastX = x; lastY = y;
5351   if((promoSweep < BlackPawn) == flipView) step = -step;
5352   if(step > 0) selectFlag = 1;
5353   if(!selectFlag) Sweep(step);
5354   return FALSE;
5355 }
5356
5357 void
5358 NextPiece (int step)
5359 {
5360     ChessSquare piece = boards[currentMove][toY][toX];
5361     do {
5362         pieceSweep -= step;
5363         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5364         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5365         if(!step) step = -1;
5366     } while(PieceToChar(pieceSweep) == '.');
5367     boards[currentMove][toY][toX] = pieceSweep;
5368     DrawPosition(FALSE, boards[currentMove]);
5369     boards[currentMove][toY][toX] = piece;
5370 }
5371 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5372 void
5373 AlphaRank (char *move, int n)
5374 {
5375 //    char *p = move, c; int x, y;
5376
5377     if (appData.debugMode) {
5378         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5379     }
5380
5381     if(move[1]=='*' &&
5382        move[2]>='0' && move[2]<='9' &&
5383        move[3]>='a' && move[3]<='x'    ) {
5384         move[1] = '@';
5385         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5386         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5387     } else
5388     if(move[0]>='0' && move[0]<='9' &&
5389        move[1]>='a' && move[1]<='x' &&
5390        move[2]>='0' && move[2]<='9' &&
5391        move[3]>='a' && move[3]<='x'    ) {
5392         /* input move, Shogi -> normal */
5393         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5394         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5395         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5396         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5397     } else
5398     if(move[1]=='@' &&
5399        move[3]>='0' && move[3]<='9' &&
5400        move[2]>='a' && move[2]<='x'    ) {
5401         move[1] = '*';
5402         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5403         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5404     } else
5405     if(
5406        move[0]>='a' && move[0]<='x' &&
5407        move[3]>='0' && move[3]<='9' &&
5408        move[2]>='a' && move[2]<='x'    ) {
5409          /* output move, normal -> Shogi */
5410         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5411         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5412         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5413         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5414         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5415     }
5416     if (appData.debugMode) {
5417         fprintf(debugFP, "   out = '%s'\n", move);
5418     }
5419 }
5420
5421 char yy_textstr[8000];
5422
5423 /* Parser for moves from gnuchess, ICS, or user typein box */
5424 Boolean
5425 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5426 {
5427     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5428
5429     switch (*moveType) {
5430       case WhitePromotion:
5431       case BlackPromotion:
5432       case WhiteNonPromotion:
5433       case BlackNonPromotion:
5434       case NormalMove:
5435       case FirstLeg:
5436       case WhiteCapturesEnPassant:
5437       case BlackCapturesEnPassant:
5438       case WhiteKingSideCastle:
5439       case WhiteQueenSideCastle:
5440       case BlackKingSideCastle:
5441       case BlackQueenSideCastle:
5442       case WhiteKingSideCastleWild:
5443       case WhiteQueenSideCastleWild:
5444       case BlackKingSideCastleWild:
5445       case BlackQueenSideCastleWild:
5446       /* Code added by Tord: */
5447       case WhiteHSideCastleFR:
5448       case WhiteASideCastleFR:
5449       case BlackHSideCastleFR:
5450       case BlackASideCastleFR:
5451       /* End of code added by Tord */
5452       case IllegalMove:         /* bug or odd chess variant */
5453         *fromX = currentMoveString[0] - AAA;
5454         *fromY = currentMoveString[1] - ONE;
5455         *toX = currentMoveString[2] - AAA;
5456         *toY = currentMoveString[3] - ONE;
5457         *promoChar = currentMoveString[4];
5458         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5459             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5460     if (appData.debugMode) {
5461         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5462     }
5463             *fromX = *fromY = *toX = *toY = 0;
5464             return FALSE;
5465         }
5466         if (appData.testLegality) {
5467           return (*moveType != IllegalMove);
5468         } else {
5469           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5470                          // [HGM] lion: if this is a double move we are less critical
5471                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5472         }
5473
5474       case WhiteDrop:
5475       case BlackDrop:
5476         *fromX = *moveType == WhiteDrop ?
5477           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5478           (int) CharToPiece(ToLower(currentMoveString[0]));
5479         *fromY = DROP_RANK;
5480         *toX = currentMoveString[2] - AAA;
5481         *toY = currentMoveString[3] - ONE;
5482         *promoChar = NULLCHAR;
5483         return TRUE;
5484
5485       case AmbiguousMove:
5486       case ImpossibleMove:
5487       case EndOfFile:
5488       case ElapsedTime:
5489       case Comment:
5490       case PGNTag:
5491       case NAG:
5492       case WhiteWins:
5493       case BlackWins:
5494       case GameIsDrawn:
5495       default:
5496     if (appData.debugMode) {
5497         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5498     }
5499         /* bug? */
5500         *fromX = *fromY = *toX = *toY = 0;
5501         *promoChar = NULLCHAR;
5502         return FALSE;
5503     }
5504 }
5505
5506 Boolean pushed = FALSE;
5507 char *lastParseAttempt;
5508
5509 void
5510 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5511 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5512   int fromX, fromY, toX, toY; char promoChar;
5513   ChessMove moveType;
5514   Boolean valid;
5515   int nr = 0;
5516
5517   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5518   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5519     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5520     pushed = TRUE;
5521   }
5522   endPV = forwardMostMove;
5523   do {
5524     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5525     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5526     lastParseAttempt = pv;
5527     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5528     if(!valid && nr == 0 &&
5529        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5530         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5531         // Hande case where played move is different from leading PV move
5532         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5533         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5534         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5535         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5536           endPV += 2; // if position different, keep this
5537           moveList[endPV-1][0] = fromX + AAA;
5538           moveList[endPV-1][1] = fromY + ONE;
5539           moveList[endPV-1][2] = toX + AAA;
5540           moveList[endPV-1][3] = toY + ONE;
5541           parseList[endPV-1][0] = NULLCHAR;
5542           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5543         }
5544       }
5545     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5546     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5547     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5548     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5549         valid++; // allow comments in PV
5550         continue;
5551     }
5552     nr++;
5553     if(endPV+1 > framePtr) break; // no space, truncate
5554     if(!valid) break;
5555     endPV++;
5556     CopyBoard(boards[endPV], boards[endPV-1]);
5557     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5558     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5559     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5560     CoordsToAlgebraic(boards[endPV - 1],
5561                              PosFlags(endPV - 1),
5562                              fromY, fromX, toY, toX, promoChar,
5563                              parseList[endPV - 1]);
5564   } while(valid);
5565   if(atEnd == 2) return; // used hidden, for PV conversion
5566   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5567   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5568   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5569                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5570   DrawPosition(TRUE, boards[currentMove]);
5571 }
5572
5573 int
5574 MultiPV (ChessProgramState *cps)
5575 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5576         int i;
5577         for(i=0; i<cps->nrOptions; i++)
5578             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5579                 return i;
5580         return -1;
5581 }
5582
5583 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5584
5585 Boolean
5586 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5587 {
5588         int startPV, multi, lineStart, origIndex = index;
5589         char *p, buf2[MSG_SIZ];
5590         ChessProgramState *cps = (pane ? &second : &first);
5591
5592         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5593         lastX = x; lastY = y;
5594         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5595         lineStart = startPV = index;
5596         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5597         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5598         index = startPV;
5599         do{ while(buf[index] && buf[index] != '\n') index++;
5600         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5601         buf[index] = 0;
5602         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5603                 int n = cps->option[multi].value;
5604                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5605                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5606                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5607                 cps->option[multi].value = n;
5608                 *start = *end = 0;
5609                 return FALSE;
5610         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5611                 ExcludeClick(origIndex - lineStart);
5612                 return FALSE;
5613         }
5614         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5615         *start = startPV; *end = index-1;
5616         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5617         return TRUE;
5618 }
5619
5620 char *
5621 PvToSAN (char *pv)
5622 {
5623         static char buf[10*MSG_SIZ];
5624         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5625         *buf = NULLCHAR;
5626         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5627         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5628         for(i = forwardMostMove; i<endPV; i++){
5629             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5630             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5631             k += strlen(buf+k);
5632         }
5633         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5634         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5635         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5636         endPV = savedEnd;
5637         return buf;
5638 }
5639
5640 Boolean
5641 LoadPV (int x, int y)
5642 { // called on right mouse click to load PV
5643   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5644   lastX = x; lastY = y;
5645   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5646   extendGame = FALSE;
5647   return TRUE;
5648 }
5649
5650 void
5651 UnLoadPV ()
5652 {
5653   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5654   if(endPV < 0) return;
5655   if(appData.autoCopyPV) CopyFENToClipboard();
5656   endPV = -1;
5657   if(extendGame && currentMove > forwardMostMove) {
5658         Boolean saveAnimate = appData.animate;
5659         if(pushed) {
5660             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5661                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5662             } else storedGames--; // abandon shelved tail of original game
5663         }
5664         pushed = FALSE;
5665         forwardMostMove = currentMove;
5666         currentMove = oldFMM;
5667         appData.animate = FALSE;
5668         ToNrEvent(forwardMostMove);
5669         appData.animate = saveAnimate;
5670   }
5671   currentMove = forwardMostMove;
5672   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5673   ClearPremoveHighlights();
5674   DrawPosition(TRUE, boards[currentMove]);
5675 }
5676
5677 void
5678 MovePV (int x, int y, int h)
5679 { // step through PV based on mouse coordinates (called on mouse move)
5680   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5681
5682   // we must somehow check if right button is still down (might be released off board!)
5683   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5684   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5685   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5686   if(!step) return;
5687   lastX = x; lastY = y;
5688
5689   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5690   if(endPV < 0) return;
5691   if(y < margin) step = 1; else
5692   if(y > h - margin) step = -1;
5693   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5694   currentMove += step;
5695   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5696   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5697                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5698   DrawPosition(FALSE, boards[currentMove]);
5699 }
5700
5701
5702 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5703 // All positions will have equal probability, but the current method will not provide a unique
5704 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5705 #define DARK 1
5706 #define LITE 2
5707 #define ANY 3
5708
5709 int squaresLeft[4];
5710 int piecesLeft[(int)BlackPawn];
5711 int seed, nrOfShuffles;
5712
5713 void
5714 GetPositionNumber ()
5715 {       // sets global variable seed
5716         int i;
5717
5718         seed = appData.defaultFrcPosition;
5719         if(seed < 0) { // randomize based on time for negative FRC position numbers
5720                 for(i=0; i<50; i++) seed += random();
5721                 seed = random() ^ random() >> 8 ^ random() << 8;
5722                 if(seed<0) seed = -seed;
5723         }
5724 }
5725
5726 int
5727 put (Board board, int pieceType, int rank, int n, int shade)
5728 // put the piece on the (n-1)-th empty squares of the given shade
5729 {
5730         int i;
5731
5732         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5733                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5734                         board[rank][i] = (ChessSquare) pieceType;
5735                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5736                         squaresLeft[ANY]--;
5737                         piecesLeft[pieceType]--;
5738                         return i;
5739                 }
5740         }
5741         return -1;
5742 }
5743
5744
5745 void
5746 AddOnePiece (Board board, int pieceType, int rank, int shade)
5747 // calculate where the next piece goes, (any empty square), and put it there
5748 {
5749         int i;
5750
5751         i = seed % squaresLeft[shade];
5752         nrOfShuffles *= squaresLeft[shade];
5753         seed /= squaresLeft[shade];
5754         put(board, pieceType, rank, i, shade);
5755 }
5756
5757 void
5758 AddTwoPieces (Board board, int pieceType, int rank)
5759 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5760 {
5761         int i, n=squaresLeft[ANY], j=n-1, k;
5762
5763         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5764         i = seed % k;  // pick one
5765         nrOfShuffles *= k;
5766         seed /= k;
5767         while(i >= j) i -= j--;
5768         j = n - 1 - j; i += j;
5769         put(board, pieceType, rank, j, ANY);
5770         put(board, pieceType, rank, i, ANY);
5771 }
5772
5773 void
5774 SetUpShuffle (Board board, int number)
5775 {
5776         int i, p, first=1;
5777
5778         GetPositionNumber(); nrOfShuffles = 1;
5779
5780         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5781         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5782         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5783
5784         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5785
5786         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5787             p = (int) board[0][i];
5788             if(p < (int) BlackPawn) piecesLeft[p] ++;
5789             board[0][i] = EmptySquare;
5790         }
5791
5792         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5793             // shuffles restricted to allow normal castling put KRR first
5794             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5795                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5796             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5797                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5798             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5799                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5800             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5801                 put(board, WhiteRook, 0, 0, ANY);
5802             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5803         }
5804
5805         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5806             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5807             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5808                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5809                 while(piecesLeft[p] >= 2) {
5810                     AddOnePiece(board, p, 0, LITE);
5811                     AddOnePiece(board, p, 0, DARK);
5812                 }
5813                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5814             }
5815
5816         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5817             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5818             // but we leave King and Rooks for last, to possibly obey FRC restriction
5819             if(p == (int)WhiteRook) continue;
5820             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5821             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5822         }
5823
5824         // now everything is placed, except perhaps King (Unicorn) and Rooks
5825
5826         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5827             // Last King gets castling rights
5828             while(piecesLeft[(int)WhiteUnicorn]) {
5829                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5830                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5831             }
5832
5833             while(piecesLeft[(int)WhiteKing]) {
5834                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5835                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5836             }
5837
5838
5839         } else {
5840             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5841             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5842         }
5843
5844         // Only Rooks can be left; simply place them all
5845         while(piecesLeft[(int)WhiteRook]) {
5846                 i = put(board, WhiteRook, 0, 0, ANY);
5847                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5848                         if(first) {
5849                                 first=0;
5850                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5851                         }
5852                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5853                 }
5854         }
5855         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5856             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5857         }
5858
5859         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5860 }
5861
5862 int
5863 SetCharTable (char *table, const char * map)
5864 /* [HGM] moved here from winboard.c because of its general usefulness */
5865 /*       Basically a safe strcpy that uses the last character as King */
5866 {
5867     int result = FALSE; int NrPieces;
5868
5869     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5870                     && NrPieces >= 12 && !(NrPieces&1)) {
5871         int i; /* [HGM] Accept even length from 12 to 34 */
5872
5873         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5874         for( i=0; i<NrPieces/2-1; i++ ) {
5875             table[i] = map[i];
5876             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5877         }
5878         table[(int) WhiteKing]  = map[NrPieces/2-1];
5879         table[(int) BlackKing]  = map[NrPieces-1];
5880
5881         result = TRUE;
5882     }
5883
5884     return result;
5885 }
5886
5887 void
5888 Prelude (Board board)
5889 {       // [HGM] superchess: random selection of exo-pieces
5890         int i, j, k; ChessSquare p;
5891         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5892
5893         GetPositionNumber(); // use FRC position number
5894
5895         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5896             SetCharTable(pieceToChar, appData.pieceToCharTable);
5897             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5898                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5899         }
5900
5901         j = seed%4;                 seed /= 4;
5902         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5903         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5904         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5905         j = seed%3 + (seed%3 >= j); seed /= 3;
5906         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5907         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5908         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5909         j = seed%3;                 seed /= 3;
5910         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5911         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5912         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5913         j = seed%2 + (seed%2 >= j); seed /= 2;
5914         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5915         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5916         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5917         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5918         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5919         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5920         put(board, exoPieces[0],    0, 0, ANY);
5921         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5922 }
5923
5924 void
5925 InitPosition (int redraw)
5926 {
5927     ChessSquare (* pieces)[BOARD_FILES];
5928     int i, j, pawnRow=1, pieceRows=1, overrule,
5929     oldx = gameInfo.boardWidth,
5930     oldy = gameInfo.boardHeight,
5931     oldh = gameInfo.holdingsWidth;
5932     static int oldv;
5933
5934     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5935
5936     /* [AS] Initialize pv info list [HGM] and game status */
5937     {
5938         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5939             pvInfoList[i].depth = 0;
5940             boards[i][EP_STATUS] = EP_NONE;
5941             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5942         }
5943
5944         initialRulePlies = 0; /* 50-move counter start */
5945
5946         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5947         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5948     }
5949
5950
5951     /* [HGM] logic here is completely changed. In stead of full positions */
5952     /* the initialized data only consist of the two backranks. The switch */
5953     /* selects which one we will use, which is than copied to the Board   */
5954     /* initialPosition, which for the rest is initialized by Pawns and    */
5955     /* empty squares. This initial position is then copied to boards[0],  */
5956     /* possibly after shuffling, so that it remains available.            */
5957
5958     gameInfo.holdingsWidth = 0; /* default board sizes */
5959     gameInfo.boardWidth    = 8;
5960     gameInfo.boardHeight   = 8;
5961     gameInfo.holdingsSize  = 0;
5962     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5963     for(i=0; i<BOARD_FILES-2; i++)
5964       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5965     initialPosition[EP_STATUS] = EP_NONE;
5966     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5967     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5968          SetCharTable(pieceNickName, appData.pieceNickNames);
5969     else SetCharTable(pieceNickName, "............");
5970     pieces = FIDEArray;
5971
5972     switch (gameInfo.variant) {
5973     case VariantFischeRandom:
5974       shuffleOpenings = TRUE;
5975     default:
5976       break;
5977     case VariantShatranj:
5978       pieces = ShatranjArray;
5979       nrCastlingRights = 0;
5980       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5981       break;
5982     case VariantMakruk:
5983       pieces = makrukArray;
5984       nrCastlingRights = 0;
5985       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5986       break;
5987     case VariantASEAN:
5988       pieces = aseanArray;
5989       nrCastlingRights = 0;
5990       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5991       break;
5992     case VariantTwoKings:
5993       pieces = twoKingsArray;
5994       break;
5995     case VariantGrand:
5996       pieces = GrandArray;
5997       nrCastlingRights = 0;
5998       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5999       gameInfo.boardWidth = 10;
6000       gameInfo.boardHeight = 10;
6001       gameInfo.holdingsSize = 7;
6002       break;
6003     case VariantCapaRandom:
6004       shuffleOpenings = TRUE;
6005     case VariantCapablanca:
6006       pieces = CapablancaArray;
6007       gameInfo.boardWidth = 10;
6008       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6009       break;
6010     case VariantGothic:
6011       pieces = GothicArray;
6012       gameInfo.boardWidth = 10;
6013       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6014       break;
6015     case VariantSChess:
6016       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6017       gameInfo.holdingsSize = 7;
6018       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6019       break;
6020     case VariantJanus:
6021       pieces = JanusArray;
6022       gameInfo.boardWidth = 10;
6023       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6024       nrCastlingRights = 6;
6025         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6026         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6027         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6028         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6029         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6030         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6031       break;
6032     case VariantFalcon:
6033       pieces = FalconArray;
6034       gameInfo.boardWidth = 10;
6035       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6036       break;
6037     case VariantXiangqi:
6038       pieces = XiangqiArray;
6039       gameInfo.boardWidth  = 9;
6040       gameInfo.boardHeight = 10;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6043       break;
6044     case VariantShogi:
6045       pieces = ShogiArray;
6046       gameInfo.boardWidth  = 9;
6047       gameInfo.boardHeight = 9;
6048       gameInfo.holdingsSize = 7;
6049       nrCastlingRights = 0;
6050       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6051       break;
6052     case VariantChu:
6053       pieces = ChuArray; pieceRows = 3;
6054       gameInfo.boardWidth  = 12;
6055       gameInfo.boardHeight = 12;
6056       nrCastlingRights = 0;
6057       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6058                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6059       break;
6060     case VariantCourier:
6061       pieces = CourierArray;
6062       gameInfo.boardWidth  = 12;
6063       nrCastlingRights = 0;
6064       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6065       break;
6066     case VariantKnightmate:
6067       pieces = KnightmateArray;
6068       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6069       break;
6070     case VariantSpartan:
6071       pieces = SpartanArray;
6072       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6073       break;
6074     case VariantLion:
6075       pieces = lionArray;
6076       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6077       break;
6078     case VariantChuChess:
6079       pieces = ChuChessArray;
6080       gameInfo.boardWidth = 10;
6081       gameInfo.boardHeight = 10;
6082       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6083       break;
6084     case VariantFairy:
6085       pieces = fairyArray;
6086       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6087       break;
6088     case VariantGreat:
6089       pieces = GreatArray;
6090       gameInfo.boardWidth = 10;
6091       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6092       gameInfo.holdingsSize = 8;
6093       break;
6094     case VariantSuper:
6095       pieces = FIDEArray;
6096       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6097       gameInfo.holdingsSize = 8;
6098       startedFromSetupPosition = TRUE;
6099       break;
6100     case VariantCrazyhouse:
6101     case VariantBughouse:
6102       pieces = FIDEArray;
6103       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6104       gameInfo.holdingsSize = 5;
6105       break;
6106     case VariantWildCastle:
6107       pieces = FIDEArray;
6108       /* !!?shuffle with kings guaranteed to be on d or e file */
6109       shuffleOpenings = 1;
6110       break;
6111     case VariantNoCastle:
6112       pieces = FIDEArray;
6113       nrCastlingRights = 0;
6114       /* !!?unconstrained back-rank shuffle */
6115       shuffleOpenings = 1;
6116       break;
6117     }
6118
6119     overrule = 0;
6120     if(appData.NrFiles >= 0) {
6121         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6122         gameInfo.boardWidth = appData.NrFiles;
6123     }
6124     if(appData.NrRanks >= 0) {
6125         gameInfo.boardHeight = appData.NrRanks;
6126     }
6127     if(appData.holdingsSize >= 0) {
6128         i = appData.holdingsSize;
6129         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6130         gameInfo.holdingsSize = i;
6131     }
6132     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6133     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6134         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6135
6136     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6137     if(pawnRow < 1) pawnRow = 1;
6138     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6139        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6140     if(gameInfo.variant == VariantChu) pawnRow = 3;
6141
6142     /* User pieceToChar list overrules defaults */
6143     if(appData.pieceToCharTable != NULL)
6144         SetCharTable(pieceToChar, appData.pieceToCharTable);
6145
6146     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6147
6148         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6149             s = (ChessSquare) 0; /* account holding counts in guard band */
6150         for( i=0; i<BOARD_HEIGHT; i++ )
6151             initialPosition[i][j] = s;
6152
6153         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6154         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6155         initialPosition[pawnRow][j] = WhitePawn;
6156         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6157         if(gameInfo.variant == VariantXiangqi) {
6158             if(j&1) {
6159                 initialPosition[pawnRow][j] =
6160                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6161                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6162                    initialPosition[2][j] = WhiteCannon;
6163                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6164                 }
6165             }
6166         }
6167         if(gameInfo.variant == VariantChu) {
6168              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6169                initialPosition[pawnRow+1][j] = WhiteCobra,
6170                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6171              for(i=1; i<pieceRows; i++) {
6172                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6173                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6174              }
6175         }
6176         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6177             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6178                initialPosition[0][j] = WhiteRook;
6179                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6180             }
6181         }
6182         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6183     }
6184     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6185     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6186
6187             j=BOARD_LEFT+1;
6188             initialPosition[1][j] = WhiteBishop;
6189             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6190             j=BOARD_RGHT-2;
6191             initialPosition[1][j] = WhiteRook;
6192             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6193     }
6194
6195     if( nrCastlingRights == -1) {
6196         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6197         /*       This sets default castling rights from none to normal corners   */
6198         /* Variants with other castling rights must set them themselves above    */
6199         nrCastlingRights = 6;
6200
6201         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6202         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6203         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6204         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6205         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6206         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6207      }
6208
6209      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6210      if(gameInfo.variant == VariantGreat) { // promotion commoners
6211         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6212         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6213         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6214         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6215      }
6216      if( gameInfo.variant == VariantSChess ) {
6217       initialPosition[1][0] = BlackMarshall;
6218       initialPosition[2][0] = BlackAngel;
6219       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6220       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6221       initialPosition[1][1] = initialPosition[2][1] =
6222       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6223      }
6224   if (appData.debugMode) {
6225     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6226   }
6227     if(shuffleOpenings) {
6228         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6229         startedFromSetupPosition = TRUE;
6230     }
6231     if(startedFromPositionFile) {
6232       /* [HGM] loadPos: use PositionFile for every new game */
6233       CopyBoard(initialPosition, filePosition);
6234       for(i=0; i<nrCastlingRights; i++)
6235           initialRights[i] = filePosition[CASTLING][i];
6236       startedFromSetupPosition = TRUE;
6237     }
6238
6239     CopyBoard(boards[0], initialPosition);
6240
6241     if(oldx != gameInfo.boardWidth ||
6242        oldy != gameInfo.boardHeight ||
6243        oldv != gameInfo.variant ||
6244        oldh != gameInfo.holdingsWidth
6245                                          )
6246             InitDrawingSizes(-2 ,0);
6247
6248     oldv = gameInfo.variant;
6249     if (redraw)
6250       DrawPosition(TRUE, boards[currentMove]);
6251 }
6252
6253 void
6254 SendBoard (ChessProgramState *cps, int moveNum)
6255 {
6256     char message[MSG_SIZ];
6257
6258     if (cps->useSetboard) {
6259       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6260       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6261       SendToProgram(message, cps);
6262       free(fen);
6263
6264     } else {
6265       ChessSquare *bp;
6266       int i, j, left=0, right=BOARD_WIDTH;
6267       /* Kludge to set black to move, avoiding the troublesome and now
6268        * deprecated "black" command.
6269        */
6270       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6271         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6272
6273       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6274
6275       SendToProgram("edit\n", cps);
6276       SendToProgram("#\n", cps);
6277       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6278         bp = &boards[moveNum][i][left];
6279         for (j = left; j < right; j++, bp++) {
6280           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6281           if ((int) *bp < (int) BlackPawn) {
6282             if(j == BOARD_RGHT+1)
6283                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6284             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6285             if(message[0] == '+' || message[0] == '~') {
6286               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6287                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6288                         AAA + j, ONE + i);
6289             }
6290             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6291                 message[1] = BOARD_RGHT   - 1 - j + '1';
6292                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6293             }
6294             SendToProgram(message, cps);
6295           }
6296         }
6297       }
6298
6299       SendToProgram("c\n", cps);
6300       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6301         bp = &boards[moveNum][i][left];
6302         for (j = left; j < right; j++, bp++) {
6303           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6304           if (((int) *bp != (int) EmptySquare)
6305               && ((int) *bp >= (int) BlackPawn)) {
6306             if(j == BOARD_LEFT-2)
6307                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6308             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6309                     AAA + j, ONE + i);
6310             if(message[0] == '+' || message[0] == '~') {
6311               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6312                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6313                         AAA + j, ONE + i);
6314             }
6315             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6316                 message[1] = BOARD_RGHT   - 1 - j + '1';
6317                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6318             }
6319             SendToProgram(message, cps);
6320           }
6321         }
6322       }
6323
6324       SendToProgram(".\n", cps);
6325     }
6326     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6327 }
6328
6329 char exclusionHeader[MSG_SIZ];
6330 int exCnt, excludePtr;
6331 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6332 static Exclusion excluTab[200];
6333 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6334
6335 static void
6336 WriteMap (int s)
6337 {
6338     int j;
6339     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6340     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6341 }
6342
6343 static void
6344 ClearMap ()
6345 {
6346     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6347     excludePtr = 24; exCnt = 0;
6348     WriteMap(0);
6349 }
6350
6351 static void
6352 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6353 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6354     char buf[2*MOVE_LEN], *p;
6355     Exclusion *e = excluTab;
6356     int i;
6357     for(i=0; i<exCnt; i++)
6358         if(e[i].ff == fromX && e[i].fr == fromY &&
6359            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6360     if(i == exCnt) { // was not in exclude list; add it
6361         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6362         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6363             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6364             return; // abort
6365         }
6366         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6367         excludePtr++; e[i].mark = excludePtr++;
6368         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6369         exCnt++;
6370     }
6371     exclusionHeader[e[i].mark] = state;
6372 }
6373
6374 static int
6375 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6376 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6377     char buf[MSG_SIZ];
6378     int j, k;
6379     ChessMove moveType;
6380     if((signed char)promoChar == -1) { // kludge to indicate best move
6381         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6382             return 1; // if unparsable, abort
6383     }
6384     // update exclusion map (resolving toggle by consulting existing state)
6385     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6386     j = k%8; k >>= 3;
6387     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6388     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6389          excludeMap[k] |=   1<<j;
6390     else excludeMap[k] &= ~(1<<j);
6391     // update header
6392     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6393     // inform engine
6394     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6395     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6396     SendToBoth(buf);
6397     return (state == '+');
6398 }
6399
6400 static void
6401 ExcludeClick (int index)
6402 {
6403     int i, j;
6404     Exclusion *e = excluTab;
6405     if(index < 25) { // none, best or tail clicked
6406         if(index < 13) { // none: include all
6407             WriteMap(0); // clear map
6408             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6409             SendToBoth("include all\n"); // and inform engine
6410         } else if(index > 18) { // tail
6411             if(exclusionHeader[19] == '-') { // tail was excluded
6412                 SendToBoth("include all\n");
6413                 WriteMap(0); // clear map completely
6414                 // now re-exclude selected moves
6415                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6416                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6417             } else { // tail was included or in mixed state
6418                 SendToBoth("exclude all\n");
6419                 WriteMap(0xFF); // fill map completely
6420                 // now re-include selected moves
6421                 j = 0; // count them
6422                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6423                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6424                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6425             }
6426         } else { // best
6427             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6428         }
6429     } else {
6430         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6431             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6432             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6433             break;
6434         }
6435     }
6436 }
6437
6438 ChessSquare
6439 DefaultPromoChoice (int white)
6440 {
6441     ChessSquare result;
6442     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6443        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6444         result = WhiteFerz; // no choice
6445     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6446         result= WhiteKing; // in Suicide Q is the last thing we want
6447     else if(gameInfo.variant == VariantSpartan)
6448         result = white ? WhiteQueen : WhiteAngel;
6449     else result = WhiteQueen;
6450     if(!white) result = WHITE_TO_BLACK result;
6451     return result;
6452 }
6453
6454 static int autoQueen; // [HGM] oneclick
6455
6456 int
6457 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6458 {
6459     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6460     /* [HGM] add Shogi promotions */
6461     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6462     ChessSquare piece, partner;
6463     ChessMove moveType;
6464     Boolean premove;
6465
6466     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6467     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6468
6469     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6470       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6471         return FALSE;
6472
6473     piece = boards[currentMove][fromY][fromX];
6474     if(gameInfo.variant == VariantChu) {
6475         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6476         promotionZoneSize = BOARD_HEIGHT/3;
6477         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6478     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6479         promotionZoneSize = BOARD_HEIGHT/3;
6480         highestPromotingPiece = (int)WhiteAlfil;
6481     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6482         promotionZoneSize = 3;
6483     }
6484
6485     // Treat Lance as Pawn when it is not representing Amazon
6486     if(gameInfo.variant != VariantSuper) {
6487         if(piece == WhiteLance) piece = WhitePawn; else
6488         if(piece == BlackLance) piece = BlackPawn;
6489     }
6490
6491     // next weed out all moves that do not touch the promotion zone at all
6492     if((int)piece >= BlackPawn) {
6493         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6494              return FALSE;
6495         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6496         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6497     } else {
6498         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6499            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6500         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6501              return FALSE;
6502     }
6503
6504     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6505
6506     // weed out mandatory Shogi promotions
6507     if(gameInfo.variant == VariantShogi) {
6508         if(piece >= BlackPawn) {
6509             if(toY == 0 && piece == BlackPawn ||
6510                toY == 0 && piece == BlackQueen ||
6511                toY <= 1 && piece == BlackKnight) {
6512                 *promoChoice = '+';
6513                 return FALSE;
6514             }
6515         } else {
6516             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6517                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6518                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6519                 *promoChoice = '+';
6520                 return FALSE;
6521             }
6522         }
6523     }
6524
6525     // weed out obviously illegal Pawn moves
6526     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6527         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6528         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6529         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6530         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6531         // note we are not allowed to test for valid (non-)capture, due to premove
6532     }
6533
6534     // we either have a choice what to promote to, or (in Shogi) whether to promote
6535     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6536        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6537         *promoChoice = PieceToChar(BlackFerz);  // no choice
6538         return FALSE;
6539     }
6540     // no sense asking what we must promote to if it is going to explode...
6541     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6542         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6543         return FALSE;
6544     }
6545     // give caller the default choice even if we will not make it
6546     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6547     partner = piece; // pieces can promote if the pieceToCharTable says so
6548     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6549     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6550     if(        sweepSelect && gameInfo.variant != VariantGreat
6551                            && gameInfo.variant != VariantGrand
6552                            && gameInfo.variant != VariantSuper) return FALSE;
6553     if(autoQueen) return FALSE; // predetermined
6554
6555     // suppress promotion popup on illegal moves that are not premoves
6556     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6557               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6558     if(appData.testLegality && !premove) {
6559         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6560                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6561         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6562         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6563             return FALSE;
6564     }
6565
6566     return TRUE;
6567 }
6568
6569 int
6570 InPalace (int row, int column)
6571 {   /* [HGM] for Xiangqi */
6572     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6573          column < (BOARD_WIDTH + 4)/2 &&
6574          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6575     return FALSE;
6576 }
6577
6578 int
6579 PieceForSquare (int x, int y)
6580 {
6581   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6582      return -1;
6583   else
6584      return boards[currentMove][y][x];
6585 }
6586
6587 int
6588 OKToStartUserMove (int x, int y)
6589 {
6590     ChessSquare from_piece;
6591     int white_piece;
6592
6593     if (matchMode) return FALSE;
6594     if (gameMode == EditPosition) return TRUE;
6595
6596     if (x >= 0 && y >= 0)
6597       from_piece = boards[currentMove][y][x];
6598     else
6599       from_piece = EmptySquare;
6600
6601     if (from_piece == EmptySquare) return FALSE;
6602
6603     white_piece = (int)from_piece >= (int)WhitePawn &&
6604       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6605
6606     switch (gameMode) {
6607       case AnalyzeFile:
6608       case TwoMachinesPlay:
6609       case EndOfGame:
6610         return FALSE;
6611
6612       case IcsObserving:
6613       case IcsIdle:
6614         return FALSE;
6615
6616       case MachinePlaysWhite:
6617       case IcsPlayingBlack:
6618         if (appData.zippyPlay) return FALSE;
6619         if (white_piece) {
6620             DisplayMoveError(_("You are playing Black"));
6621             return FALSE;
6622         }
6623         break;
6624
6625       case MachinePlaysBlack:
6626       case IcsPlayingWhite:
6627         if (appData.zippyPlay) return FALSE;
6628         if (!white_piece) {
6629             DisplayMoveError(_("You are playing White"));
6630             return FALSE;
6631         }
6632         break;
6633
6634       case PlayFromGameFile:
6635             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6636       case EditGame:
6637         if (!white_piece && WhiteOnMove(currentMove)) {
6638             DisplayMoveError(_("It is White's turn"));
6639             return FALSE;
6640         }
6641         if (white_piece && !WhiteOnMove(currentMove)) {
6642             DisplayMoveError(_("It is Black's turn"));
6643             return FALSE;
6644         }
6645         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6646             /* Editing correspondence game history */
6647             /* Could disallow this or prompt for confirmation */
6648             cmailOldMove = -1;
6649         }
6650         break;
6651
6652       case BeginningOfGame:
6653         if (appData.icsActive) return FALSE;
6654         if (!appData.noChessProgram) {
6655             if (!white_piece) {
6656                 DisplayMoveError(_("You are playing White"));
6657                 return FALSE;
6658             }
6659         }
6660         break;
6661
6662       case Training:
6663         if (!white_piece && WhiteOnMove(currentMove)) {
6664             DisplayMoveError(_("It is White's turn"));
6665             return FALSE;
6666         }
6667         if (white_piece && !WhiteOnMove(currentMove)) {
6668             DisplayMoveError(_("It is Black's turn"));
6669             return FALSE;
6670         }
6671         break;
6672
6673       default:
6674       case IcsExamining:
6675         break;
6676     }
6677     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6678         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6679         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6680         && gameMode != AnalyzeFile && gameMode != Training) {
6681         DisplayMoveError(_("Displayed position is not current"));
6682         return FALSE;
6683     }
6684     return TRUE;
6685 }
6686
6687 Boolean
6688 OnlyMove (int *x, int *y, Boolean captures)
6689 {
6690     DisambiguateClosure cl;
6691     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6692     switch(gameMode) {
6693       case MachinePlaysBlack:
6694       case IcsPlayingWhite:
6695       case BeginningOfGame:
6696         if(!WhiteOnMove(currentMove)) return FALSE;
6697         break;
6698       case MachinePlaysWhite:
6699       case IcsPlayingBlack:
6700         if(WhiteOnMove(currentMove)) return FALSE;
6701         break;
6702       case EditGame:
6703         break;
6704       default:
6705         return FALSE;
6706     }
6707     cl.pieceIn = EmptySquare;
6708     cl.rfIn = *y;
6709     cl.ffIn = *x;
6710     cl.rtIn = -1;
6711     cl.ftIn = -1;
6712     cl.promoCharIn = NULLCHAR;
6713     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6714     if( cl.kind == NormalMove ||
6715         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6716         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6717         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6718       fromX = cl.ff;
6719       fromY = cl.rf;
6720       *x = cl.ft;
6721       *y = cl.rt;
6722       return TRUE;
6723     }
6724     if(cl.kind != ImpossibleMove) return FALSE;
6725     cl.pieceIn = EmptySquare;
6726     cl.rfIn = -1;
6727     cl.ffIn = -1;
6728     cl.rtIn = *y;
6729     cl.ftIn = *x;
6730     cl.promoCharIn = NULLCHAR;
6731     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6732     if( cl.kind == NormalMove ||
6733         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6734         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6735         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6736       fromX = cl.ff;
6737       fromY = cl.rf;
6738       *x = cl.ft;
6739       *y = cl.rt;
6740       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6741       return TRUE;
6742     }
6743     return FALSE;
6744 }
6745
6746 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6747 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6748 int lastLoadGameUseList = FALSE;
6749 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6750 ChessMove lastLoadGameStart = EndOfFile;
6751 int doubleClick;
6752
6753 void
6754 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6755 {
6756     ChessMove moveType;
6757     ChessSquare pup;
6758     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6759
6760     /* Check if the user is playing in turn.  This is complicated because we
6761        let the user "pick up" a piece before it is his turn.  So the piece he
6762        tried to pick up may have been captured by the time he puts it down!
6763        Therefore we use the color the user is supposed to be playing in this
6764        test, not the color of the piece that is currently on the starting
6765        square---except in EditGame mode, where the user is playing both
6766        sides; fortunately there the capture race can't happen.  (It can
6767        now happen in IcsExamining mode, but that's just too bad.  The user
6768        will get a somewhat confusing message in that case.)
6769        */
6770
6771     switch (gameMode) {
6772       case AnalyzeFile:
6773       case TwoMachinesPlay:
6774       case EndOfGame:
6775       case IcsObserving:
6776       case IcsIdle:
6777         /* We switched into a game mode where moves are not accepted,
6778            perhaps while the mouse button was down. */
6779         return;
6780
6781       case MachinePlaysWhite:
6782         /* User is moving for Black */
6783         if (WhiteOnMove(currentMove)) {
6784             DisplayMoveError(_("It is White's turn"));
6785             return;
6786         }
6787         break;
6788
6789       case MachinePlaysBlack:
6790         /* User is moving for White */
6791         if (!WhiteOnMove(currentMove)) {
6792             DisplayMoveError(_("It is Black's turn"));
6793             return;
6794         }
6795         break;
6796
6797       case PlayFromGameFile:
6798             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6799       case EditGame:
6800       case IcsExamining:
6801       case BeginningOfGame:
6802       case AnalyzeMode:
6803       case Training:
6804         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6805         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6806             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6807             /* User is moving for Black */
6808             if (WhiteOnMove(currentMove)) {
6809                 DisplayMoveError(_("It is White's turn"));
6810                 return;
6811             }
6812         } else {
6813             /* User is moving for White */
6814             if (!WhiteOnMove(currentMove)) {
6815                 DisplayMoveError(_("It is Black's turn"));
6816                 return;
6817             }
6818         }
6819         break;
6820
6821       case IcsPlayingBlack:
6822         /* User is moving for Black */
6823         if (WhiteOnMove(currentMove)) {
6824             if (!appData.premove) {
6825                 DisplayMoveError(_("It is White's turn"));
6826             } else if (toX >= 0 && toY >= 0) {
6827                 premoveToX = toX;
6828                 premoveToY = toY;
6829                 premoveFromX = fromX;
6830                 premoveFromY = fromY;
6831                 premovePromoChar = promoChar;
6832                 gotPremove = 1;
6833                 if (appData.debugMode)
6834                     fprintf(debugFP, "Got premove: fromX %d,"
6835                             "fromY %d, toX %d, toY %d\n",
6836                             fromX, fromY, toX, toY);
6837             }
6838             return;
6839         }
6840         break;
6841
6842       case IcsPlayingWhite:
6843         /* User is moving for White */
6844         if (!WhiteOnMove(currentMove)) {
6845             if (!appData.premove) {
6846                 DisplayMoveError(_("It is Black's turn"));
6847             } else if (toX >= 0 && toY >= 0) {
6848                 premoveToX = toX;
6849                 premoveToY = toY;
6850                 premoveFromX = fromX;
6851                 premoveFromY = fromY;
6852                 premovePromoChar = promoChar;
6853                 gotPremove = 1;
6854                 if (appData.debugMode)
6855                     fprintf(debugFP, "Got premove: fromX %d,"
6856                             "fromY %d, toX %d, toY %d\n",
6857                             fromX, fromY, toX, toY);
6858             }
6859             return;
6860         }
6861         break;
6862
6863       default:
6864         break;
6865
6866       case EditPosition:
6867         /* EditPosition, empty square, or different color piece;
6868            click-click move is possible */
6869         if (toX == -2 || toY == -2) {
6870             boards[0][fromY][fromX] = EmptySquare;
6871             DrawPosition(FALSE, boards[currentMove]);
6872             return;
6873         } else if (toX >= 0 && toY >= 0) {
6874             boards[0][toY][toX] = boards[0][fromY][fromX];
6875             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6876                 if(boards[0][fromY][0] != EmptySquare) {
6877                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6878                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6879                 }
6880             } else
6881             if(fromX == BOARD_RGHT+1) {
6882                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6883                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6884                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6885                 }
6886             } else
6887             boards[0][fromY][fromX] = gatingPiece;
6888             DrawPosition(FALSE, boards[currentMove]);
6889             return;
6890         }
6891         return;
6892     }
6893
6894     if(toX < 0 || toY < 0) return;
6895     pup = boards[currentMove][toY][toX];
6896
6897     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6898     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6899          if( pup != EmptySquare ) return;
6900          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6901            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6902                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6903            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6904            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6905            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6906            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6907          fromY = DROP_RANK;
6908     }
6909
6910     /* [HGM] always test for legality, to get promotion info */
6911     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6912                                          fromY, fromX, toY, toX, promoChar);
6913
6914     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6915
6916     /* [HGM] but possibly ignore an IllegalMove result */
6917     if (appData.testLegality) {
6918         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6919             DisplayMoveError(_("Illegal move"));
6920             return;
6921         }
6922     }
6923
6924     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6925         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6926              ClearPremoveHighlights(); // was included
6927         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6928         return;
6929     }
6930
6931     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6932 }
6933
6934 /* Common tail of UserMoveEvent and DropMenuEvent */
6935 int
6936 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6937 {
6938     char *bookHit = 0;
6939
6940     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6941         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6942         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6943         if(WhiteOnMove(currentMove)) {
6944             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6945         } else {
6946             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6947         }
6948     }
6949
6950     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6951        move type in caller when we know the move is a legal promotion */
6952     if(moveType == NormalMove && promoChar)
6953         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6954
6955     /* [HGM] <popupFix> The following if has been moved here from
6956        UserMoveEvent(). Because it seemed to belong here (why not allow
6957        piece drops in training games?), and because it can only be
6958        performed after it is known to what we promote. */
6959     if (gameMode == Training) {
6960       /* compare the move played on the board to the next move in the
6961        * game. If they match, display the move and the opponent's response.
6962        * If they don't match, display an error message.
6963        */
6964       int saveAnimate;
6965       Board testBoard;
6966       CopyBoard(testBoard, boards[currentMove]);
6967       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6968
6969       if (CompareBoards(testBoard, boards[currentMove+1])) {
6970         ForwardInner(currentMove+1);
6971
6972         /* Autoplay the opponent's response.
6973          * if appData.animate was TRUE when Training mode was entered,
6974          * the response will be animated.
6975          */
6976         saveAnimate = appData.animate;
6977         appData.animate = animateTraining;
6978         ForwardInner(currentMove+1);
6979         appData.animate = saveAnimate;
6980
6981         /* check for the end of the game */
6982         if (currentMove >= forwardMostMove) {
6983           gameMode = PlayFromGameFile;
6984           ModeHighlight();
6985           SetTrainingModeOff();
6986           DisplayInformation(_("End of game"));
6987         }
6988       } else {
6989         DisplayError(_("Incorrect move"), 0);
6990       }
6991       return 1;
6992     }
6993
6994   /* Ok, now we know that the move is good, so we can kill
6995      the previous line in Analysis Mode */
6996   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6997                                 && currentMove < forwardMostMove) {
6998     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6999     else forwardMostMove = currentMove;
7000   }
7001
7002   ClearMap();
7003
7004   /* If we need the chess program but it's dead, restart it */
7005   ResurrectChessProgram();
7006
7007   /* A user move restarts a paused game*/
7008   if (pausing)
7009     PauseEvent();
7010
7011   thinkOutput[0] = NULLCHAR;
7012
7013   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7014
7015   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7016     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7017     return 1;
7018   }
7019
7020   if (gameMode == BeginningOfGame) {
7021     if (appData.noChessProgram) {
7022       gameMode = EditGame;
7023       SetGameInfo();
7024     } else {
7025       char buf[MSG_SIZ];
7026       gameMode = MachinePlaysBlack;
7027       StartClocks();
7028       SetGameInfo();
7029       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7030       DisplayTitle(buf);
7031       if (first.sendName) {
7032         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7033         SendToProgram(buf, &first);
7034       }
7035       StartClocks();
7036     }
7037     ModeHighlight();
7038   }
7039
7040   /* Relay move to ICS or chess engine */
7041   if (appData.icsActive) {
7042     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7043         gameMode == IcsExamining) {
7044       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7045         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7046         SendToICS("draw ");
7047         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7048       }
7049       // also send plain move, in case ICS does not understand atomic claims
7050       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7051       ics_user_moved = 1;
7052     }
7053   } else {
7054     if (first.sendTime && (gameMode == BeginningOfGame ||
7055                            gameMode == MachinePlaysWhite ||
7056                            gameMode == MachinePlaysBlack)) {
7057       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7058     }
7059     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7060          // [HGM] book: if program might be playing, let it use book
7061         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7062         first.maybeThinking = TRUE;
7063     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7064         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7065         SendBoard(&first, currentMove+1);
7066         if(second.analyzing) {
7067             if(!second.useSetboard) SendToProgram("undo\n", &second);
7068             SendBoard(&second, currentMove+1);
7069         }
7070     } else {
7071         SendMoveToProgram(forwardMostMove-1, &first);
7072         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7073     }
7074     if (currentMove == cmailOldMove + 1) {
7075       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7076     }
7077   }
7078
7079   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7080
7081   switch (gameMode) {
7082   case EditGame:
7083     if(appData.testLegality)
7084     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7085     case MT_NONE:
7086     case MT_CHECK:
7087       break;
7088     case MT_CHECKMATE:
7089     case MT_STAINMATE:
7090       if (WhiteOnMove(currentMove)) {
7091         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7092       } else {
7093         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7094       }
7095       break;
7096     case MT_STALEMATE:
7097       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7098       break;
7099     }
7100     break;
7101
7102   case MachinePlaysBlack:
7103   case MachinePlaysWhite:
7104     /* disable certain menu options while machine is thinking */
7105     SetMachineThinkingEnables();
7106     break;
7107
7108   default:
7109     break;
7110   }
7111
7112   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7113   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7114
7115   if(bookHit) { // [HGM] book: simulate book reply
7116         static char bookMove[MSG_SIZ]; // a bit generous?
7117
7118         programStats.nodes = programStats.depth = programStats.time =
7119         programStats.score = programStats.got_only_move = 0;
7120         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7121
7122         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7123         strcat(bookMove, bookHit);
7124         HandleMachineMove(bookMove, &first);
7125   }
7126   return 1;
7127 }
7128
7129 void
7130 MarkByFEN(char *fen)
7131 {
7132         int r, f;
7133         if(!appData.markers || !appData.highlightDragging) return;
7134         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7135         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7136         while(*fen) {
7137             int s = 0;
7138             marker[r][f] = 0;
7139             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7140             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7141             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7142             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7143             if(*fen == 'T') marker[r][f++] = 0; else
7144             if(*fen == 'Y') marker[r][f++] = 1; else
7145             if(*fen == 'G') marker[r][f++] = 3; else
7146             if(*fen == 'B') marker[r][f++] = 4; else
7147             if(*fen == 'C') marker[r][f++] = 5; else
7148             if(*fen == 'M') marker[r][f++] = 6; else
7149             if(*fen == 'W') marker[r][f++] = 7; else
7150             if(*fen == 'D') marker[r][f++] = 8; else
7151             if(*fen == 'R') marker[r][f++] = 2; else {
7152                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7153               f += s; fen -= s>0;
7154             }
7155             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7156             if(r < 0) break;
7157             fen++;
7158         }
7159         DrawPosition(TRUE, NULL);
7160 }
7161
7162 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7163
7164 void
7165 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7166 {
7167     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7168     Markers *m = (Markers *) closure;
7169     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7170         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7171                          || kind == WhiteCapturesEnPassant
7172                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7173     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7174 }
7175
7176 void
7177 MarkTargetSquares (int clear)
7178 {
7179   int x, y, sum=0;
7180   if(clear) { // no reason to ever suppress clearing
7181     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7182     if(!sum) return; // nothing was cleared,no redraw needed
7183   } else {
7184     int capt = 0;
7185     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7186        !appData.testLegality || gameMode == EditPosition) return;
7187     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7188     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7189       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7190       if(capt)
7191       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7192     }
7193   }
7194   DrawPosition(FALSE, NULL);
7195 }
7196
7197 int
7198 Explode (Board board, int fromX, int fromY, int toX, int toY)
7199 {
7200     if(gameInfo.variant == VariantAtomic &&
7201        (board[toY][toX] != EmptySquare ||                     // capture?
7202         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7203                          board[fromY][fromX] == BlackPawn   )
7204       )) {
7205         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7206         return TRUE;
7207     }
7208     return FALSE;
7209 }
7210
7211 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7212
7213 int
7214 CanPromote (ChessSquare piece, int y)
7215 {
7216         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7217         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7218         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7219         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7220            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7221            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7222          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7223         return (piece == BlackPawn && y <= zone ||
7224                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7225                 piece == BlackLance && y == 1 ||
7226                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7227 }
7228
7229 void
7230 HoverEvent (int xPix, int yPix, int x, int y)
7231 {
7232         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7233         int r, f;
7234         if(!first.highlight) return;
7235         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7236         if(x == oldX && y == oldY) return; // only do something if we enter new square
7237         oldFromX = fromX; oldFromY = fromY;
7238         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7239           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7240             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7241         else if(oldX != x || oldY != y) {
7242           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7243           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7244             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7245           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7246             char buf[MSG_SIZ];
7247             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7248             SendToProgram(buf, &first);
7249           }
7250           oldX = x; oldY = y;
7251 //        SetHighlights(fromX, fromY, x, y);
7252         }
7253 }
7254
7255 void ReportClick(char *action, int x, int y)
7256 {
7257         char buf[MSG_SIZ]; // Inform engine of what user does
7258         int r, f;
7259         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7260           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7261         if(!first.highlight || gameMode == EditPosition) return;
7262         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7263         SendToProgram(buf, &first);
7264 }
7265
7266 void
7267 LeftClick (ClickType clickType, int xPix, int yPix)
7268 {
7269     int x, y;
7270     Boolean saveAnimate;
7271     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7272     char promoChoice = NULLCHAR;
7273     ChessSquare piece;
7274     static TimeMark lastClickTime, prevClickTime;
7275
7276     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7277
7278     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7279
7280     if (clickType == Press) ErrorPopDown();
7281     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7282
7283     x = EventToSquare(xPix, BOARD_WIDTH);
7284     y = EventToSquare(yPix, BOARD_HEIGHT);
7285     if (!flipView && y >= 0) {
7286         y = BOARD_HEIGHT - 1 - y;
7287     }
7288     if (flipView && x >= 0) {
7289         x = BOARD_WIDTH - 1 - x;
7290     }
7291
7292     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7293         defaultPromoChoice = promoSweep;
7294         promoSweep = EmptySquare;   // terminate sweep
7295         promoDefaultAltered = TRUE;
7296         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7297     }
7298
7299     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7300         if(clickType == Release) return; // ignore upclick of click-click destination
7301         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7302         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7303         if(gameInfo.holdingsWidth &&
7304                 (WhiteOnMove(currentMove)
7305                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7306                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7307             // click in right holdings, for determining promotion piece
7308             ChessSquare p = boards[currentMove][y][x];
7309             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7310             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7311             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7312                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7313                 fromX = fromY = -1;
7314                 return;
7315             }
7316         }
7317         DrawPosition(FALSE, boards[currentMove]);
7318         return;
7319     }
7320
7321     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7322     if(clickType == Press
7323             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7324               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7325               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7326         return;
7327
7328     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7329         // could be static click on premove from-square: abort premove
7330         gotPremove = 0;
7331         ClearPremoveHighlights();
7332     }
7333
7334     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7335         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7336
7337     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7338         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7339                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7340         defaultPromoChoice = DefaultPromoChoice(side);
7341     }
7342
7343     autoQueen = appData.alwaysPromoteToQueen;
7344
7345     if (fromX == -1) {
7346       int originalY = y;
7347       gatingPiece = EmptySquare;
7348       if (clickType != Press) {
7349         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7350             DragPieceEnd(xPix, yPix); dragging = 0;
7351             DrawPosition(FALSE, NULL);
7352         }
7353         return;
7354       }
7355       doubleClick = FALSE;
7356       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7357         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7358       }
7359       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7360       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7361          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7362          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7363             /* First square */
7364             if (OKToStartUserMove(fromX, fromY)) {
7365                 second = 0;
7366                 ReportClick("lift", x, y);
7367                 MarkTargetSquares(0);
7368                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7369                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7370                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7371                     promoSweep = defaultPromoChoice;
7372                     selectFlag = 0; lastX = xPix; lastY = yPix;
7373                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7374                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7375                 }
7376                 if (appData.highlightDragging) {
7377                     SetHighlights(fromX, fromY, -1, -1);
7378                 } else {
7379                     ClearHighlights();
7380                 }
7381             } else fromX = fromY = -1;
7382             return;
7383         }
7384     }
7385
7386     /* fromX != -1 */
7387     if (clickType == Press && gameMode != EditPosition) {
7388         ChessSquare fromP;
7389         ChessSquare toP;
7390         int frc;
7391
7392         // ignore off-board to clicks
7393         if(y < 0 || x < 0) return;
7394
7395         /* Check if clicking again on the same color piece */
7396         fromP = boards[currentMove][fromY][fromX];
7397         toP = boards[currentMove][y][x];
7398         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7399         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7400            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7401              WhitePawn <= toP && toP <= WhiteKing &&
7402              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7403              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7404             (BlackPawn <= fromP && fromP <= BlackKing &&
7405              BlackPawn <= toP && toP <= BlackKing &&
7406              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7407              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7408             /* Clicked again on same color piece -- changed his mind */
7409             second = (x == fromX && y == fromY);
7410             killX = killY = -1;
7411             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7412                 second = FALSE; // first double-click rather than scond click
7413                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7414             }
7415             promoDefaultAltered = FALSE;
7416             MarkTargetSquares(1);
7417            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7418             if (appData.highlightDragging) {
7419                 SetHighlights(x, y, -1, -1);
7420             } else {
7421                 ClearHighlights();
7422             }
7423             if (OKToStartUserMove(x, y)) {
7424                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7425                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7426                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7427                  gatingPiece = boards[currentMove][fromY][fromX];
7428                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7429                 fromX = x;
7430                 fromY = y; dragging = 1;
7431                 ReportClick("lift", x, y);
7432                 MarkTargetSquares(0);
7433                 DragPieceBegin(xPix, yPix, FALSE);
7434                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7435                     promoSweep = defaultPromoChoice;
7436                     selectFlag = 0; lastX = xPix; lastY = yPix;
7437                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7438                 }
7439             }
7440            }
7441            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7442            second = FALSE;
7443         }
7444         // ignore clicks on holdings
7445         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7446     }
7447
7448     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7449         DragPieceEnd(xPix, yPix); dragging = 0;
7450         if(clearFlag) {
7451             // a deferred attempt to click-click move an empty square on top of a piece
7452             boards[currentMove][y][x] = EmptySquare;
7453             ClearHighlights();
7454             DrawPosition(FALSE, boards[currentMove]);
7455             fromX = fromY = -1; clearFlag = 0;
7456             return;
7457         }
7458         if (appData.animateDragging) {
7459             /* Undo animation damage if any */
7460             DrawPosition(FALSE, NULL);
7461         }
7462         if (second || sweepSelecting) {
7463             /* Second up/down in same square; just abort move */
7464             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7465             second = sweepSelecting = 0;
7466             fromX = fromY = -1;
7467             gatingPiece = EmptySquare;
7468             MarkTargetSquares(1);
7469             ClearHighlights();
7470             gotPremove = 0;
7471             ClearPremoveHighlights();
7472         } else {
7473             /* First upclick in same square; start click-click mode */
7474             SetHighlights(x, y, -1, -1);
7475         }
7476         return;
7477     }
7478
7479     clearFlag = 0;
7480
7481     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7482         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7483         DisplayMessage(_("only marked squares are legal"),"");
7484         DrawPosition(TRUE, NULL);
7485         return; // ignore to-click
7486     }
7487
7488     /* we now have a different from- and (possibly off-board) to-square */
7489     /* Completed move */
7490     if(!sweepSelecting) {
7491         toX = x;
7492         toY = y;
7493     }
7494
7495     piece = boards[currentMove][fromY][fromX];
7496
7497     saveAnimate = appData.animate;
7498     if (clickType == Press) {
7499         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7500         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7501             // must be Edit Position mode with empty-square selected
7502             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7503             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7504             return;
7505         }
7506         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7507             return;
7508         }
7509         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7510             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7511         } else
7512         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7513         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7514           if(appData.sweepSelect) {
7515             promoSweep = defaultPromoChoice;
7516             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7517             selectFlag = 0; lastX = xPix; lastY = yPix;
7518             Sweep(0); // Pawn that is going to promote: preview promotion piece
7519             sweepSelecting = 1;
7520             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7521             MarkTargetSquares(1);
7522           }
7523           return; // promo popup appears on up-click
7524         }
7525         /* Finish clickclick move */
7526         if (appData.animate || appData.highlightLastMove) {
7527             SetHighlights(fromX, fromY, toX, toY);
7528         } else {
7529             ClearHighlights();
7530         }
7531     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7532         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7533         if (appData.animate || appData.highlightLastMove) {
7534             SetHighlights(fromX, fromY, toX, toY);
7535         } else {
7536             ClearHighlights();
7537         }
7538     } else {
7539 #if 0
7540 // [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
7541         /* Finish drag move */
7542         if (appData.highlightLastMove) {
7543             SetHighlights(fromX, fromY, toX, toY);
7544         } else {
7545             ClearHighlights();
7546         }
7547 #endif
7548         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7549         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7550           dragging *= 2;            // flag button-less dragging if we are dragging
7551           MarkTargetSquares(1);
7552           if(x == killX && y == killY) killX = killY = -1; else {
7553             killX = x; killY = y;     //remeber this square as intermediate
7554             ReportClick("put", x, y); // and inform engine
7555             ReportClick("lift", x, y);
7556             MarkTargetSquares(0);
7557             return;
7558           }
7559         }
7560         DragPieceEnd(xPix, yPix); dragging = 0;
7561         /* Don't animate move and drag both */
7562         appData.animate = FALSE;
7563     }
7564
7565     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7566     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7567         ChessSquare piece = boards[currentMove][fromY][fromX];
7568         if(gameMode == EditPosition && piece != EmptySquare &&
7569            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7570             int n;
7571
7572             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7573                 n = PieceToNumber(piece - (int)BlackPawn);
7574                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7575                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7576                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7577             } else
7578             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7579                 n = PieceToNumber(piece);
7580                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7581                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7582                 boards[currentMove][n][BOARD_WIDTH-2]++;
7583             }
7584             boards[currentMove][fromY][fromX] = EmptySquare;
7585         }
7586         ClearHighlights();
7587         fromX = fromY = -1;
7588         MarkTargetSquares(1);
7589         DrawPosition(TRUE, boards[currentMove]);
7590         return;
7591     }
7592
7593     // off-board moves should not be highlighted
7594     if(x < 0 || y < 0) ClearHighlights();
7595     else ReportClick("put", x, y);
7596
7597     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7598
7599     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7600         SetHighlights(fromX, fromY, toX, toY);
7601         MarkTargetSquares(1);
7602         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7603             // [HGM] super: promotion to captured piece selected from holdings
7604             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7605             promotionChoice = TRUE;
7606             // kludge follows to temporarily execute move on display, without promoting yet
7607             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7608             boards[currentMove][toY][toX] = p;
7609             DrawPosition(FALSE, boards[currentMove]);
7610             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7611             boards[currentMove][toY][toX] = q;
7612             DisplayMessage("Click in holdings to choose piece", "");
7613             return;
7614         }
7615         PromotionPopUp(promoChoice);
7616     } else {
7617         int oldMove = currentMove;
7618         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7619         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7620         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7621         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7622            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7623             DrawPosition(TRUE, boards[currentMove]);
7624         MarkTargetSquares(1);
7625         fromX = fromY = -1;
7626     }
7627     appData.animate = saveAnimate;
7628     if (appData.animate || appData.animateDragging) {
7629         /* Undo animation damage if needed */
7630         DrawPosition(FALSE, NULL);
7631     }
7632 }
7633
7634 int
7635 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7636 {   // front-end-free part taken out of PieceMenuPopup
7637     int whichMenu; int xSqr, ySqr;
7638
7639     if(seekGraphUp) { // [HGM] seekgraph
7640         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7641         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7642         return -2;
7643     }
7644
7645     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7646          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7647         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7648         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7649         if(action == Press)   {
7650             originalFlip = flipView;
7651             flipView = !flipView; // temporarily flip board to see game from partners perspective
7652             DrawPosition(TRUE, partnerBoard);
7653             DisplayMessage(partnerStatus, "");
7654             partnerUp = TRUE;
7655         } else if(action == Release) {
7656             flipView = originalFlip;
7657             DrawPosition(TRUE, boards[currentMove]);
7658             partnerUp = FALSE;
7659         }
7660         return -2;
7661     }
7662
7663     xSqr = EventToSquare(x, BOARD_WIDTH);
7664     ySqr = EventToSquare(y, BOARD_HEIGHT);
7665     if (action == Release) {
7666         if(pieceSweep != EmptySquare) {
7667             EditPositionMenuEvent(pieceSweep, toX, toY);
7668             pieceSweep = EmptySquare;
7669         } else UnLoadPV(); // [HGM] pv
7670     }
7671     if (action != Press) return -2; // return code to be ignored
7672     switch (gameMode) {
7673       case IcsExamining:
7674         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7675       case EditPosition:
7676         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7677         if (xSqr < 0 || ySqr < 0) return -1;
7678         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7679         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7680         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7681         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7682         NextPiece(0);
7683         return 2; // grab
7684       case IcsObserving:
7685         if(!appData.icsEngineAnalyze) return -1;
7686       case IcsPlayingWhite:
7687       case IcsPlayingBlack:
7688         if(!appData.zippyPlay) goto noZip;
7689       case AnalyzeMode:
7690       case AnalyzeFile:
7691       case MachinePlaysWhite:
7692       case MachinePlaysBlack:
7693       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7694         if (!appData.dropMenu) {
7695           LoadPV(x, y);
7696           return 2; // flag front-end to grab mouse events
7697         }
7698         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7699            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7700       case EditGame:
7701       noZip:
7702         if (xSqr < 0 || ySqr < 0) return -1;
7703         if (!appData.dropMenu || appData.testLegality &&
7704             gameInfo.variant != VariantBughouse &&
7705             gameInfo.variant != VariantCrazyhouse) return -1;
7706         whichMenu = 1; // drop menu
7707         break;
7708       default:
7709         return -1;
7710     }
7711
7712     if (((*fromX = xSqr) < 0) ||
7713         ((*fromY = ySqr) < 0)) {
7714         *fromX = *fromY = -1;
7715         return -1;
7716     }
7717     if (flipView)
7718       *fromX = BOARD_WIDTH - 1 - *fromX;
7719     else
7720       *fromY = BOARD_HEIGHT - 1 - *fromY;
7721
7722     return whichMenu;
7723 }
7724
7725 void
7726 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7727 {
7728 //    char * hint = lastHint;
7729     FrontEndProgramStats stats;
7730
7731     stats.which = cps == &first ? 0 : 1;
7732     stats.depth = cpstats->depth;
7733     stats.nodes = cpstats->nodes;
7734     stats.score = cpstats->score;
7735     stats.time = cpstats->time;
7736     stats.pv = cpstats->movelist;
7737     stats.hint = lastHint;
7738     stats.an_move_index = 0;
7739     stats.an_move_count = 0;
7740
7741     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7742         stats.hint = cpstats->move_name;
7743         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7744         stats.an_move_count = cpstats->nr_moves;
7745     }
7746
7747     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
7748
7749     SetProgramStats( &stats );
7750 }
7751
7752 void
7753 ClearEngineOutputPane (int which)
7754 {
7755     static FrontEndProgramStats dummyStats;
7756     dummyStats.which = which;
7757     dummyStats.pv = "#";
7758     SetProgramStats( &dummyStats );
7759 }
7760
7761 #define MAXPLAYERS 500
7762
7763 char *
7764 TourneyStandings (int display)
7765 {
7766     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7767     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7768     char result, *p, *names[MAXPLAYERS];
7769
7770     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7771         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7772     names[0] = p = strdup(appData.participants);
7773     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7774
7775     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7776
7777     while(result = appData.results[nr]) {
7778         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7779         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7780         wScore = bScore = 0;
7781         switch(result) {
7782           case '+': wScore = 2; break;
7783           case '-': bScore = 2; break;
7784           case '=': wScore = bScore = 1; break;
7785           case ' ':
7786           case '*': return strdup("busy"); // tourney not finished
7787         }
7788         score[w] += wScore;
7789         score[b] += bScore;
7790         games[w]++;
7791         games[b]++;
7792         nr++;
7793     }
7794     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7795     for(w=0; w<nPlayers; w++) {
7796         bScore = -1;
7797         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7798         ranking[w] = b; points[w] = bScore; score[b] = -2;
7799     }
7800     p = malloc(nPlayers*34+1);
7801     for(w=0; w<nPlayers && w<display; w++)
7802         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7803     free(names[0]);
7804     return p;
7805 }
7806
7807 void
7808 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7809 {       // count all piece types
7810         int p, f, r;
7811         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7812         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7813         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7814                 p = board[r][f];
7815                 pCnt[p]++;
7816                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7817                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7818                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7819                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7820                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7821                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7822         }
7823 }
7824
7825 int
7826 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7827 {
7828         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7829         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7830
7831         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7832         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7833         if(myPawns == 2 && nMine == 3) // KPP
7834             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7835         if(myPawns == 1 && nMine == 2) // KP
7836             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7837         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7838             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7839         if(myPawns) return FALSE;
7840         if(pCnt[WhiteRook+side])
7841             return pCnt[BlackRook-side] ||
7842                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7843                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7844                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7845         if(pCnt[WhiteCannon+side]) {
7846             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7847             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7848         }
7849         if(pCnt[WhiteKnight+side])
7850             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7851         return FALSE;
7852 }
7853
7854 int
7855 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7856 {
7857         VariantClass v = gameInfo.variant;
7858
7859         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7860         if(v == VariantShatranj) return TRUE; // always winnable through baring
7861         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7862         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7863
7864         if(v == VariantXiangqi) {
7865                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7866
7867                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7868                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7869                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7870                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7871                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7872                 if(stale) // we have at least one last-rank P plus perhaps C
7873                     return majors // KPKX
7874                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7875                 else // KCA*E*
7876                     return pCnt[WhiteFerz+side] // KCAK
7877                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7878                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7879                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7880
7881         } else if(v == VariantKnightmate) {
7882                 if(nMine == 1) return FALSE;
7883                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7884         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7885                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7886
7887                 if(nMine == 1) return FALSE; // bare King
7888                 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
7889                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7890                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7891                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7892                 if(pCnt[WhiteKnight+side])
7893                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7894                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7895                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7896                 if(nBishops)
7897                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7898                 if(pCnt[WhiteAlfil+side])
7899                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7900                 if(pCnt[WhiteWazir+side])
7901                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7902         }
7903
7904         return TRUE;
7905 }
7906
7907 int
7908 CompareWithRights (Board b1, Board b2)
7909 {
7910     int rights = 0;
7911     if(!CompareBoards(b1, b2)) return FALSE;
7912     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7913     /* compare castling rights */
7914     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7915            rights++; /* King lost rights, while rook still had them */
7916     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7917         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7918            rights++; /* but at least one rook lost them */
7919     }
7920     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7921            rights++;
7922     if( b1[CASTLING][5] != NoRights ) {
7923         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7924            rights++;
7925     }
7926     return rights == 0;
7927 }
7928
7929 int
7930 Adjudicate (ChessProgramState *cps)
7931 {       // [HGM] some adjudications useful with buggy engines
7932         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7933         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7934         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7935         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7936         int k, drop, count = 0; static int bare = 1;
7937         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7938         Boolean canAdjudicate = !appData.icsActive;
7939
7940         // most tests only when we understand the game, i.e. legality-checking on
7941             if( appData.testLegality )
7942             {   /* [HGM] Some more adjudications for obstinate engines */
7943                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7944                 static int moveCount = 6;
7945                 ChessMove result;
7946                 char *reason = NULL;
7947
7948                 /* Count what is on board. */
7949                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7950
7951                 /* Some material-based adjudications that have to be made before stalemate test */
7952                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7953                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7954                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7955                      if(canAdjudicate && appData.checkMates) {
7956                          if(engineOpponent)
7957                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7958                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7959                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7960                          return 1;
7961                      }
7962                 }
7963
7964                 /* Bare King in Shatranj (loses) or Losers (wins) */
7965                 if( nrW == 1 || nrB == 1) {
7966                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7967                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7968                      if(canAdjudicate && appData.checkMates) {
7969                          if(engineOpponent)
7970                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7971                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7972                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7973                          return 1;
7974                      }
7975                   } else
7976                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7977                   {    /* bare King */
7978                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7979                         if(canAdjudicate && appData.checkMates) {
7980                             /* but only adjudicate if adjudication enabled */
7981                             if(engineOpponent)
7982                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7983                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7984                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7985                             return 1;
7986                         }
7987                   }
7988                 } else bare = 1;
7989
7990
7991             // don't wait for engine to announce game end if we can judge ourselves
7992             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7993               case MT_CHECK:
7994                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7995                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7996                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7997                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7998                             checkCnt++;
7999                         if(checkCnt >= 2) {
8000                             reason = "Xboard adjudication: 3rd check";
8001                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8002                             break;
8003                         }
8004                     }
8005                 }
8006               case MT_NONE:
8007               default:
8008                 break;
8009               case MT_STALEMATE:
8010               case MT_STAINMATE:
8011                 reason = "Xboard adjudication: Stalemate";
8012                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8013                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8014                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8015                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8016                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8017                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8018                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8019                                                                         EP_CHECKMATE : EP_WINS);
8020                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8021                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8022                 }
8023                 break;
8024               case MT_CHECKMATE:
8025                 reason = "Xboard adjudication: Checkmate";
8026                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8027                 if(gameInfo.variant == VariantShogi) {
8028                     if(forwardMostMove > backwardMostMove
8029                        && moveList[forwardMostMove-1][1] == '@'
8030                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8031                         reason = "XBoard adjudication: pawn-drop mate";
8032                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8033                     }
8034                 }
8035                 break;
8036             }
8037
8038                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8039                     case EP_STALEMATE:
8040                         result = GameIsDrawn; break;
8041                     case EP_CHECKMATE:
8042                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8043                     case EP_WINS:
8044                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8045                     default:
8046                         result = EndOfFile;
8047                 }
8048                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8049                     if(engineOpponent)
8050                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8051                     GameEnds( result, reason, GE_XBOARD );
8052                     return 1;
8053                 }
8054
8055                 /* Next absolutely insufficient mating material. */
8056                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8057                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8058                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8059
8060                      /* always flag draws, for judging claims */
8061                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8062
8063                      if(canAdjudicate && appData.materialDraws) {
8064                          /* but only adjudicate them if adjudication enabled */
8065                          if(engineOpponent) {
8066                            SendToProgram("force\n", engineOpponent); // suppress reply
8067                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8068                          }
8069                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8070                          return 1;
8071                      }
8072                 }
8073
8074                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8075                 if(gameInfo.variant == VariantXiangqi ?
8076                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8077                  : nrW + nrB == 4 &&
8078                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8079                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8080                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8081                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8082                    ) ) {
8083                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8084                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8085                           if(engineOpponent) {
8086                             SendToProgram("force\n", engineOpponent); // suppress reply
8087                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8088                           }
8089                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8090                           return 1;
8091                      }
8092                 } else moveCount = 6;
8093             }
8094
8095         // Repetition draws and 50-move rule can be applied independently of legality testing
8096
8097                 /* Check for rep-draws */
8098                 count = 0;
8099                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8100                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8101                 for(k = forwardMostMove-2;
8102                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8103                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8104                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8105                     k-=2)
8106                 {   int rights=0;
8107                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8108                         /* compare castling rights */
8109                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8110                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8111                                 rights++; /* King lost rights, while rook still had them */
8112                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8113                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8114                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8115                                    rights++; /* but at least one rook lost them */
8116                         }
8117                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8118                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8119                                 rights++;
8120                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8121                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8122                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8123                                    rights++;
8124                         }
8125                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8126                             && appData.drawRepeats > 1) {
8127                              /* adjudicate after user-specified nr of repeats */
8128                              int result = GameIsDrawn;
8129                              char *details = "XBoard adjudication: repetition draw";
8130                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8131                                 // [HGM] xiangqi: check for forbidden perpetuals
8132                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8133                                 for(m=forwardMostMove; m>k; m-=2) {
8134                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8135                                         ourPerpetual = 0; // the current mover did not always check
8136                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8137                                         hisPerpetual = 0; // the opponent did not always check
8138                                 }
8139                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8140                                                                         ourPerpetual, hisPerpetual);
8141                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8142                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8143                                     details = "Xboard adjudication: perpetual checking";
8144                                 } else
8145                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8146                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8147                                 } else
8148                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8149                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8150                                         result = BlackWins;
8151                                         details = "Xboard adjudication: repetition";
8152                                     }
8153                                 } else // it must be XQ
8154                                 // Now check for perpetual chases
8155                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8156                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8157                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8158                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8159                                         static char resdet[MSG_SIZ];
8160                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8161                                         details = resdet;
8162                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8163                                     } else
8164                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8165                                         break; // Abort repetition-checking loop.
8166                                 }
8167                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8168                              }
8169                              if(engineOpponent) {
8170                                SendToProgram("force\n", engineOpponent); // suppress reply
8171                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8172                              }
8173                              GameEnds( result, details, GE_XBOARD );
8174                              return 1;
8175                         }
8176                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8177                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8178                     }
8179                 }
8180
8181                 /* Now we test for 50-move draws. Determine ply count */
8182                 count = forwardMostMove;
8183                 /* look for last irreversble move */
8184                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8185                     count--;
8186                 /* if we hit starting position, add initial plies */
8187                 if( count == backwardMostMove )
8188                     count -= initialRulePlies;
8189                 count = forwardMostMove - count;
8190                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8191                         // adjust reversible move counter for checks in Xiangqi
8192                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8193                         if(i < backwardMostMove) i = backwardMostMove;
8194                         while(i <= forwardMostMove) {
8195                                 lastCheck = inCheck; // check evasion does not count
8196                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8197                                 if(inCheck || lastCheck) count--; // check does not count
8198                                 i++;
8199                         }
8200                 }
8201                 if( count >= 100)
8202                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8203                          /* this is used to judge if draw claims are legal */
8204                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8205                          if(engineOpponent) {
8206                            SendToProgram("force\n", engineOpponent); // suppress reply
8207                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8208                          }
8209                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8210                          return 1;
8211                 }
8212
8213                 /* if draw offer is pending, treat it as a draw claim
8214                  * when draw condition present, to allow engines a way to
8215                  * claim draws before making their move to avoid a race
8216                  * condition occurring after their move
8217                  */
8218                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8219                          char *p = NULL;
8220                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8221                              p = "Draw claim: 50-move rule";
8222                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8223                              p = "Draw claim: 3-fold repetition";
8224                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8225                              p = "Draw claim: insufficient mating material";
8226                          if( p != NULL && canAdjudicate) {
8227                              if(engineOpponent) {
8228                                SendToProgram("force\n", engineOpponent); // suppress reply
8229                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8230                              }
8231                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8232                              return 1;
8233                          }
8234                 }
8235
8236                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8237                     if(engineOpponent) {
8238                       SendToProgram("force\n", engineOpponent); // suppress reply
8239                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8240                     }
8241                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8242                     return 1;
8243                 }
8244         return 0;
8245 }
8246
8247 char *
8248 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8249 {   // [HGM] book: this routine intercepts moves to simulate book replies
8250     char *bookHit = NULL;
8251
8252     //first determine if the incoming move brings opponent into his book
8253     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8254         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8255     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8256     if(bookHit != NULL && !cps->bookSuspend) {
8257         // make sure opponent is not going to reply after receiving move to book position
8258         SendToProgram("force\n", cps);
8259         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8260     }
8261     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8262     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8263     // now arrange restart after book miss
8264     if(bookHit) {
8265         // after a book hit we never send 'go', and the code after the call to this routine
8266         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8267         char buf[MSG_SIZ], *move = bookHit;
8268         if(cps->useSAN) {
8269             int fromX, fromY, toX, toY;
8270             char promoChar;
8271             ChessMove moveType;
8272             move = buf + 30;
8273             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8274                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8275                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8276                                     PosFlags(forwardMostMove),
8277                                     fromY, fromX, toY, toX, promoChar, move);
8278             } else {
8279                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8280                 bookHit = NULL;
8281             }
8282         }
8283         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8284         SendToProgram(buf, cps);
8285         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8286     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8287         SendToProgram("go\n", cps);
8288         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8289     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8290         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8291             SendToProgram("go\n", cps);
8292         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8293     }
8294     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8295 }
8296
8297 int
8298 LoadError (char *errmess, ChessProgramState *cps)
8299 {   // unloads engine and switches back to -ncp mode if it was first
8300     if(cps->initDone) return FALSE;
8301     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8302     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8303     cps->pr = NoProc;
8304     if(cps == &first) {
8305         appData.noChessProgram = TRUE;
8306         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8307         gameMode = BeginningOfGame; ModeHighlight();
8308         SetNCPMode();
8309     }
8310     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8311     DisplayMessage("", ""); // erase waiting message
8312     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8313     return TRUE;
8314 }
8315
8316 char *savedMessage;
8317 ChessProgramState *savedState;
8318 void
8319 DeferredBookMove (void)
8320 {
8321         if(savedState->lastPing != savedState->lastPong)
8322                     ScheduleDelayedEvent(DeferredBookMove, 10);
8323         else
8324         HandleMachineMove(savedMessage, savedState);
8325 }
8326
8327 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8328 static ChessProgramState *stalledEngine;
8329 static char stashedInputMove[MSG_SIZ];
8330
8331 void
8332 HandleMachineMove (char *message, ChessProgramState *cps)
8333 {
8334     static char firstLeg[20];
8335     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8336     char realname[MSG_SIZ];
8337     int fromX, fromY, toX, toY;
8338     ChessMove moveType;
8339     char promoChar, roar;
8340     char *p, *pv=buf1;
8341     int machineWhite, oldError;
8342     char *bookHit;
8343
8344     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8345         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8346         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8347             DisplayError(_("Invalid pairing from pairing engine"), 0);
8348             return;
8349         }
8350         pairingReceived = 1;
8351         NextMatchGame();
8352         return; // Skim the pairing messages here.
8353     }
8354
8355     oldError = cps->userError; cps->userError = 0;
8356
8357 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8358     /*
8359      * Kludge to ignore BEL characters
8360      */
8361     while (*message == '\007') message++;
8362
8363     /*
8364      * [HGM] engine debug message: ignore lines starting with '#' character
8365      */
8366     if(cps->debug && *message == '#') return;
8367
8368     /*
8369      * Look for book output
8370      */
8371     if (cps == &first && bookRequested) {
8372         if (message[0] == '\t' || message[0] == ' ') {
8373             /* Part of the book output is here; append it */
8374             strcat(bookOutput, message);
8375             strcat(bookOutput, "  \n");
8376             return;
8377         } else if (bookOutput[0] != NULLCHAR) {
8378             /* All of book output has arrived; display it */
8379             char *p = bookOutput;
8380             while (*p != NULLCHAR) {
8381                 if (*p == '\t') *p = ' ';
8382                 p++;
8383             }
8384             DisplayInformation(bookOutput);
8385             bookRequested = FALSE;
8386             /* Fall through to parse the current output */
8387         }
8388     }
8389
8390     /*
8391      * Look for machine move.
8392      */
8393     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8394         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8395     {
8396         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8397             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8398             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8399             stalledEngine = cps;
8400             if(appData.ponderNextMove) { // bring opponent out of ponder
8401                 if(gameMode == TwoMachinesPlay) {
8402                     if(cps->other->pause)
8403                         PauseEngine(cps->other);
8404                     else
8405                         SendToProgram("easy\n", cps->other);
8406                 }
8407             }
8408             StopClocks();
8409             return;
8410         }
8411
8412         /* This method is only useful on engines that support ping */
8413         if (cps->lastPing != cps->lastPong) {
8414           if (gameMode == BeginningOfGame) {
8415             /* Extra move from before last new; ignore */
8416             if (appData.debugMode) {
8417                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8418             }
8419           } else {
8420             if (appData.debugMode) {
8421                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8422                         cps->which, gameMode);
8423             }
8424
8425             SendToProgram("undo\n", cps);
8426           }
8427           return;
8428         }
8429
8430         switch (gameMode) {
8431           case BeginningOfGame:
8432             /* Extra move from before last reset; ignore */
8433             if (appData.debugMode) {
8434                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8435             }
8436             return;
8437
8438           case EndOfGame:
8439           case IcsIdle:
8440           default:
8441             /* Extra move after we tried to stop.  The mode test is
8442                not a reliable way of detecting this problem, but it's
8443                the best we can do on engines that don't support ping.
8444             */
8445             if (appData.debugMode) {
8446                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8447                         cps->which, gameMode);
8448             }
8449             SendToProgram("undo\n", cps);
8450             return;
8451
8452           case MachinePlaysWhite:
8453           case IcsPlayingWhite:
8454             machineWhite = TRUE;
8455             break;
8456
8457           case MachinePlaysBlack:
8458           case IcsPlayingBlack:
8459             machineWhite = FALSE;
8460             break;
8461
8462           case TwoMachinesPlay:
8463             machineWhite = (cps->twoMachinesColor[0] == 'w');
8464             break;
8465         }
8466         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8467             if (appData.debugMode) {
8468                 fprintf(debugFP,
8469                         "Ignoring move out of turn by %s, gameMode %d"
8470                         ", forwardMost %d\n",
8471                         cps->which, gameMode, forwardMostMove);
8472             }
8473             return;
8474         }
8475
8476         if(cps->alphaRank) AlphaRank(machineMove, 4);
8477
8478         // [HGM] lion: (some very limited) support for Alien protocol
8479         killX = killY = -1;
8480         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8481             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8482             return;
8483         } else if(firstLeg[0]) { // there was a previous leg;
8484             // only support case where same piece makes two step (and don't even test that!)
8485             char buf[20], *p = machineMove+1, *q = buf+1, f;
8486             safeStrCpy(buf, machineMove, 20);
8487             while(isdigit(*q)) q++; // find start of to-square
8488             safeStrCpy(machineMove, firstLeg, 20);
8489             while(isdigit(*p)) p++;
8490             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8491             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8492             firstLeg[0] = NULLCHAR;
8493         }
8494
8495         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8496                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8497             /* Machine move could not be parsed; ignore it. */
8498           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8499                     machineMove, _(cps->which));
8500             DisplayMoveError(buf1);
8501             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8502                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8503             if (gameMode == TwoMachinesPlay) {
8504               GameEnds(machineWhite ? BlackWins : WhiteWins,
8505                        buf1, GE_XBOARD);
8506             }
8507             return;
8508         }
8509
8510         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8511         /* So we have to redo legality test with true e.p. status here,  */
8512         /* to make sure an illegal e.p. capture does not slip through,   */
8513         /* to cause a forfeit on a justified illegal-move complaint      */
8514         /* of the opponent.                                              */
8515         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8516            ChessMove moveType;
8517            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8518                              fromY, fromX, toY, toX, promoChar);
8519             if(moveType == IllegalMove) {
8520               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8521                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8522                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8523                            buf1, GE_XBOARD);
8524                 return;
8525            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8526            /* [HGM] Kludge to handle engines that send FRC-style castling
8527               when they shouldn't (like TSCP-Gothic) */
8528            switch(moveType) {
8529              case WhiteASideCastleFR:
8530              case BlackASideCastleFR:
8531                toX+=2;
8532                currentMoveString[2]++;
8533                break;
8534              case WhiteHSideCastleFR:
8535              case BlackHSideCastleFR:
8536                toX--;
8537                currentMoveString[2]--;
8538                break;
8539              default: ; // nothing to do, but suppresses warning of pedantic compilers
8540            }
8541         }
8542         hintRequested = FALSE;
8543         lastHint[0] = NULLCHAR;
8544         bookRequested = FALSE;
8545         /* Program may be pondering now */
8546         cps->maybeThinking = TRUE;
8547         if (cps->sendTime == 2) cps->sendTime = 1;
8548         if (cps->offeredDraw) cps->offeredDraw--;
8549
8550         /* [AS] Save move info*/
8551         pvInfoList[ forwardMostMove ].score = programStats.score;
8552         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8553         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8554
8555         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8556
8557         /* Test suites abort the 'game' after one move */
8558         if(*appData.finger) {
8559            static FILE *f;
8560            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8561            if(!f) f = fopen(appData.finger, "w");
8562            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8563            else { DisplayFatalError("Bad output file", errno, 0); return; }
8564            free(fen);
8565            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8566         }
8567
8568         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8569         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8570             int count = 0;
8571
8572             while( count < adjudicateLossPlies ) {
8573                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8574
8575                 if( count & 1 ) {
8576                     score = -score; /* Flip score for winning side */
8577                 }
8578
8579                 if( score > adjudicateLossThreshold ) {
8580                     break;
8581                 }
8582
8583                 count++;
8584             }
8585
8586             if( count >= adjudicateLossPlies ) {
8587                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8588
8589                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8590                     "Xboard adjudication",
8591                     GE_XBOARD );
8592
8593                 return;
8594             }
8595         }
8596
8597         if(Adjudicate(cps)) {
8598             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8599             return; // [HGM] adjudicate: for all automatic game ends
8600         }
8601
8602 #if ZIPPY
8603         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8604             first.initDone) {
8605           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8606                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8607                 SendToICS("draw ");
8608                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8609           }
8610           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8611           ics_user_moved = 1;
8612           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8613                 char buf[3*MSG_SIZ];
8614
8615                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8616                         programStats.score / 100.,
8617                         programStats.depth,
8618                         programStats.time / 100.,
8619                         (unsigned int)programStats.nodes,
8620                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8621                         programStats.movelist);
8622                 SendToICS(buf);
8623 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8624           }
8625         }
8626 #endif
8627
8628         /* [AS] Clear stats for next move */
8629         ClearProgramStats();
8630         thinkOutput[0] = NULLCHAR;
8631         hiddenThinkOutputState = 0;
8632
8633         bookHit = NULL;
8634         if (gameMode == TwoMachinesPlay) {
8635             /* [HGM] relaying draw offers moved to after reception of move */
8636             /* and interpreting offer as claim if it brings draw condition */
8637             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8638                 SendToProgram("draw\n", cps->other);
8639             }
8640             if (cps->other->sendTime) {
8641                 SendTimeRemaining(cps->other,
8642                                   cps->other->twoMachinesColor[0] == 'w');
8643             }
8644             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8645             if (firstMove && !bookHit) {
8646                 firstMove = FALSE;
8647                 if (cps->other->useColors) {
8648                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8649                 }
8650                 SendToProgram("go\n", cps->other);
8651             }
8652             cps->other->maybeThinking = TRUE;
8653         }
8654
8655         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8656
8657         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8658
8659         if (!pausing && appData.ringBellAfterMoves) {
8660             if(!roar) RingBell();
8661         }
8662
8663         /*
8664          * Reenable menu items that were disabled while
8665          * machine was thinking
8666          */
8667         if (gameMode != TwoMachinesPlay)
8668             SetUserThinkingEnables();
8669
8670         // [HGM] book: after book hit opponent has received move and is now in force mode
8671         // force the book reply into it, and then fake that it outputted this move by jumping
8672         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8673         if(bookHit) {
8674                 static char bookMove[MSG_SIZ]; // a bit generous?
8675
8676                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8677                 strcat(bookMove, bookHit);
8678                 message = bookMove;
8679                 cps = cps->other;
8680                 programStats.nodes = programStats.depth = programStats.time =
8681                 programStats.score = programStats.got_only_move = 0;
8682                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8683
8684                 if(cps->lastPing != cps->lastPong) {
8685                     savedMessage = message; // args for deferred call
8686                     savedState = cps;
8687                     ScheduleDelayedEvent(DeferredBookMove, 10);
8688                     return;
8689                 }
8690                 goto FakeBookMove;
8691         }
8692
8693         return;
8694     }
8695
8696     /* Set special modes for chess engines.  Later something general
8697      *  could be added here; for now there is just one kludge feature,
8698      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8699      *  when "xboard" is given as an interactive command.
8700      */
8701     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8702         cps->useSigint = FALSE;
8703         cps->useSigterm = FALSE;
8704     }
8705     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8706       ParseFeatures(message+8, cps);
8707       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8708     }
8709
8710     if (!strncmp(message, "setup ", 6) && 
8711         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8712           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8713                                         ) { // [HGM] allow first engine to define opening position
8714       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8715       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8716       *buf = NULLCHAR;
8717       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8718       if(startedFromSetupPosition) return;
8719       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8720       if(dummy >= 3) {
8721         while(message[s] && message[s++] != ' ');
8722         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8723            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8724             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8725             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8726           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8727           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8728         }
8729       }
8730       ParseFEN(boards[0], &dummy, message+s, FALSE);
8731       DrawPosition(TRUE, boards[0]);
8732       startedFromSetupPosition = TRUE;
8733       return;
8734     }
8735     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8736      * want this, I was asked to put it in, and obliged.
8737      */
8738     if (!strncmp(message, "setboard ", 9)) {
8739         Board initial_position;
8740
8741         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8742
8743         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8744             DisplayError(_("Bad FEN received from engine"), 0);
8745             return ;
8746         } else {
8747            Reset(TRUE, FALSE);
8748            CopyBoard(boards[0], initial_position);
8749            initialRulePlies = FENrulePlies;
8750            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8751            else gameMode = MachinePlaysBlack;
8752            DrawPosition(FALSE, boards[currentMove]);
8753         }
8754         return;
8755     }
8756
8757     /*
8758      * Look for communication commands
8759      */
8760     if (!strncmp(message, "telluser ", 9)) {
8761         if(message[9] == '\\' && message[10] == '\\')
8762             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8763         PlayTellSound();
8764         DisplayNote(message + 9);
8765         return;
8766     }
8767     if (!strncmp(message, "tellusererror ", 14)) {
8768         cps->userError = 1;
8769         if(message[14] == '\\' && message[15] == '\\')
8770             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8771         PlayTellSound();
8772         DisplayError(message + 14, 0);
8773         return;
8774     }
8775     if (!strncmp(message, "tellopponent ", 13)) {
8776       if (appData.icsActive) {
8777         if (loggedOn) {
8778           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8779           SendToICS(buf1);
8780         }
8781       } else {
8782         DisplayNote(message + 13);
8783       }
8784       return;
8785     }
8786     if (!strncmp(message, "tellothers ", 11)) {
8787       if (appData.icsActive) {
8788         if (loggedOn) {
8789           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8790           SendToICS(buf1);
8791         }
8792       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8793       return;
8794     }
8795     if (!strncmp(message, "tellall ", 8)) {
8796       if (appData.icsActive) {
8797         if (loggedOn) {
8798           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8799           SendToICS(buf1);
8800         }
8801       } else {
8802         DisplayNote(message + 8);
8803       }
8804       return;
8805     }
8806     if (strncmp(message, "warning", 7) == 0) {
8807         /* Undocumented feature, use tellusererror in new code */
8808         DisplayError(message, 0);
8809         return;
8810     }
8811     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8812         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8813         strcat(realname, " query");
8814         AskQuestion(realname, buf2, buf1, cps->pr);
8815         return;
8816     }
8817     /* Commands from the engine directly to ICS.  We don't allow these to be
8818      *  sent until we are logged on. Crafty kibitzes have been known to
8819      *  interfere with the login process.
8820      */
8821     if (loggedOn) {
8822         if (!strncmp(message, "tellics ", 8)) {
8823             SendToICS(message + 8);
8824             SendToICS("\n");
8825             return;
8826         }
8827         if (!strncmp(message, "tellicsnoalias ", 15)) {
8828             SendToICS(ics_prefix);
8829             SendToICS(message + 15);
8830             SendToICS("\n");
8831             return;
8832         }
8833         /* The following are for backward compatibility only */
8834         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8835             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8836             SendToICS(ics_prefix);
8837             SendToICS(message);
8838             SendToICS("\n");
8839             return;
8840         }
8841     }
8842     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8843         if(initPing == cps->lastPong) {
8844             if(gameInfo.variant == VariantUnknown) {
8845                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8846                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8847                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8848             }
8849             initPing = -1;
8850         }
8851         return;
8852     }
8853     if(!strncmp(message, "highlight ", 10)) {
8854         if(appData.testLegality && appData.markers) return;
8855         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8856         return;
8857     }
8858     if(!strncmp(message, "click ", 6)) {
8859         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8860         if(appData.testLegality || !appData.oneClick) return;
8861         sscanf(message+6, "%c%d%c", &f, &y, &c);
8862         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8863         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8864         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8865         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8866         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8867         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8868             LeftClick(Release, lastLeftX, lastLeftY);
8869         controlKey  = (c == ',');
8870         LeftClick(Press, x, y);
8871         LeftClick(Release, x, y);
8872         first.highlight = f;
8873         return;
8874     }
8875     /*
8876      * If the move is illegal, cancel it and redraw the board.
8877      * Also deal with other error cases.  Matching is rather loose
8878      * here to accommodate engines written before the spec.
8879      */
8880     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8881         strncmp(message, "Error", 5) == 0) {
8882         if (StrStr(message, "name") ||
8883             StrStr(message, "rating") || StrStr(message, "?") ||
8884             StrStr(message, "result") || StrStr(message, "board") ||
8885             StrStr(message, "bk") || StrStr(message, "computer") ||
8886             StrStr(message, "variant") || StrStr(message, "hint") ||
8887             StrStr(message, "random") || StrStr(message, "depth") ||
8888             StrStr(message, "accepted")) {
8889             return;
8890         }
8891         if (StrStr(message, "protover")) {
8892           /* Program is responding to input, so it's apparently done
8893              initializing, and this error message indicates it is
8894              protocol version 1.  So we don't need to wait any longer
8895              for it to initialize and send feature commands. */
8896           FeatureDone(cps, 1);
8897           cps->protocolVersion = 1;
8898           return;
8899         }
8900         cps->maybeThinking = FALSE;
8901
8902         if (StrStr(message, "draw")) {
8903             /* Program doesn't have "draw" command */
8904             cps->sendDrawOffers = 0;
8905             return;
8906         }
8907         if (cps->sendTime != 1 &&
8908             (StrStr(message, "time") || StrStr(message, "otim"))) {
8909           /* Program apparently doesn't have "time" or "otim" command */
8910           cps->sendTime = 0;
8911           return;
8912         }
8913         if (StrStr(message, "analyze")) {
8914             cps->analysisSupport = FALSE;
8915             cps->analyzing = FALSE;
8916 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8917             EditGameEvent(); // [HGM] try to preserve loaded game
8918             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8919             DisplayError(buf2, 0);
8920             return;
8921         }
8922         if (StrStr(message, "(no matching move)st")) {
8923           /* Special kludge for GNU Chess 4 only */
8924           cps->stKludge = TRUE;
8925           SendTimeControl(cps, movesPerSession, timeControl,
8926                           timeIncrement, appData.searchDepth,
8927                           searchTime);
8928           return;
8929         }
8930         if (StrStr(message, "(no matching move)sd")) {
8931           /* Special kludge for GNU Chess 4 only */
8932           cps->sdKludge = TRUE;
8933           SendTimeControl(cps, movesPerSession, timeControl,
8934                           timeIncrement, appData.searchDepth,
8935                           searchTime);
8936           return;
8937         }
8938         if (!StrStr(message, "llegal")) {
8939             return;
8940         }
8941         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8942             gameMode == IcsIdle) return;
8943         if (forwardMostMove <= backwardMostMove) return;
8944         if (pausing) PauseEvent();
8945       if(appData.forceIllegal) {
8946             // [HGM] illegal: machine refused move; force position after move into it
8947           SendToProgram("force\n", cps);
8948           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8949                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8950                 // when black is to move, while there might be nothing on a2 or black
8951                 // might already have the move. So send the board as if white has the move.
8952                 // But first we must change the stm of the engine, as it refused the last move
8953                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8954                 if(WhiteOnMove(forwardMostMove)) {
8955                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8956                     SendBoard(cps, forwardMostMove); // kludgeless board
8957                 } else {
8958                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8959                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8960                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8961                 }
8962           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8963             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8964                  gameMode == TwoMachinesPlay)
8965               SendToProgram("go\n", cps);
8966             return;
8967       } else
8968         if (gameMode == PlayFromGameFile) {
8969             /* Stop reading this game file */
8970             gameMode = EditGame;
8971             ModeHighlight();
8972         }
8973         /* [HGM] illegal-move claim should forfeit game when Xboard */
8974         /* only passes fully legal moves                            */
8975         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8976             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8977                                 "False illegal-move claim", GE_XBOARD );
8978             return; // do not take back move we tested as valid
8979         }
8980         currentMove = forwardMostMove-1;
8981         DisplayMove(currentMove-1); /* before DisplayMoveError */
8982         SwitchClocks(forwardMostMove-1); // [HGM] race
8983         DisplayBothClocks();
8984         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8985                 parseList[currentMove], _(cps->which));
8986         DisplayMoveError(buf1);
8987         DrawPosition(FALSE, boards[currentMove]);
8988
8989         SetUserThinkingEnables();
8990         return;
8991     }
8992     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8993         /* Program has a broken "time" command that
8994            outputs a string not ending in newline.
8995            Don't use it. */
8996         cps->sendTime = 0;
8997     }
8998
8999     /*
9000      * If chess program startup fails, exit with an error message.
9001      * Attempts to recover here are futile. [HGM] Well, we try anyway
9002      */
9003     if ((StrStr(message, "unknown host") != NULL)
9004         || (StrStr(message, "No remote directory") != NULL)
9005         || (StrStr(message, "not found") != NULL)
9006         || (StrStr(message, "No such file") != NULL)
9007         || (StrStr(message, "can't alloc") != NULL)
9008         || (StrStr(message, "Permission denied") != NULL)) {
9009
9010         cps->maybeThinking = FALSE;
9011         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9012                 _(cps->which), cps->program, cps->host, message);
9013         RemoveInputSource(cps->isr);
9014         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9015             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9016             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9017         }
9018         return;
9019     }
9020
9021     /*
9022      * Look for hint output
9023      */
9024     if (sscanf(message, "Hint: %s", buf1) == 1) {
9025         if (cps == &first && hintRequested) {
9026             hintRequested = FALSE;
9027             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9028                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9029                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9030                                     PosFlags(forwardMostMove),
9031                                     fromY, fromX, toY, toX, promoChar, buf1);
9032                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9033                 DisplayInformation(buf2);
9034             } else {
9035                 /* Hint move could not be parsed!? */
9036               snprintf(buf2, sizeof(buf2),
9037                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9038                         buf1, _(cps->which));
9039                 DisplayError(buf2, 0);
9040             }
9041         } else {
9042           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9043         }
9044         return;
9045     }
9046
9047     /*
9048      * Ignore other messages if game is not in progress
9049      */
9050     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9051         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9052
9053     /*
9054      * look for win, lose, draw, or draw offer
9055      */
9056     if (strncmp(message, "1-0", 3) == 0) {
9057         char *p, *q, *r = "";
9058         p = strchr(message, '{');
9059         if (p) {
9060             q = strchr(p, '}');
9061             if (q) {
9062                 *q = NULLCHAR;
9063                 r = p + 1;
9064             }
9065         }
9066         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9067         return;
9068     } else if (strncmp(message, "0-1", 3) == 0) {
9069         char *p, *q, *r = "";
9070         p = strchr(message, '{');
9071         if (p) {
9072             q = strchr(p, '}');
9073             if (q) {
9074                 *q = NULLCHAR;
9075                 r = p + 1;
9076             }
9077         }
9078         /* Kludge for Arasan 4.1 bug */
9079         if (strcmp(r, "Black resigns") == 0) {
9080             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9081             return;
9082         }
9083         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9084         return;
9085     } else if (strncmp(message, "1/2", 3) == 0) {
9086         char *p, *q, *r = "";
9087         p = strchr(message, '{');
9088         if (p) {
9089             q = strchr(p, '}');
9090             if (q) {
9091                 *q = NULLCHAR;
9092                 r = p + 1;
9093             }
9094         }
9095
9096         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9097         return;
9098
9099     } else if (strncmp(message, "White resign", 12) == 0) {
9100         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9101         return;
9102     } else if (strncmp(message, "Black resign", 12) == 0) {
9103         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9104         return;
9105     } else if (strncmp(message, "White matches", 13) == 0 ||
9106                strncmp(message, "Black matches", 13) == 0   ) {
9107         /* [HGM] ignore GNUShogi noises */
9108         return;
9109     } else if (strncmp(message, "White", 5) == 0 &&
9110                message[5] != '(' &&
9111                StrStr(message, "Black") == NULL) {
9112         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9113         return;
9114     } else if (strncmp(message, "Black", 5) == 0 &&
9115                message[5] != '(') {
9116         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9117         return;
9118     } else if (strcmp(message, "resign") == 0 ||
9119                strcmp(message, "computer resigns") == 0) {
9120         switch (gameMode) {
9121           case MachinePlaysBlack:
9122           case IcsPlayingBlack:
9123             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9124             break;
9125           case MachinePlaysWhite:
9126           case IcsPlayingWhite:
9127             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9128             break;
9129           case TwoMachinesPlay:
9130             if (cps->twoMachinesColor[0] == 'w')
9131               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9132             else
9133               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9134             break;
9135           default:
9136             /* can't happen */
9137             break;
9138         }
9139         return;
9140     } else if (strncmp(message, "opponent mates", 14) == 0) {
9141         switch (gameMode) {
9142           case MachinePlaysBlack:
9143           case IcsPlayingBlack:
9144             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9145             break;
9146           case MachinePlaysWhite:
9147           case IcsPlayingWhite:
9148             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9149             break;
9150           case TwoMachinesPlay:
9151             if (cps->twoMachinesColor[0] == 'w')
9152               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9153             else
9154               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9155             break;
9156           default:
9157             /* can't happen */
9158             break;
9159         }
9160         return;
9161     } else if (strncmp(message, "computer mates", 14) == 0) {
9162         switch (gameMode) {
9163           case MachinePlaysBlack:
9164           case IcsPlayingBlack:
9165             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9166             break;
9167           case MachinePlaysWhite:
9168           case IcsPlayingWhite:
9169             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9170             break;
9171           case TwoMachinesPlay:
9172             if (cps->twoMachinesColor[0] == 'w')
9173               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9174             else
9175               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9176             break;
9177           default:
9178             /* can't happen */
9179             break;
9180         }
9181         return;
9182     } else if (strncmp(message, "checkmate", 9) == 0) {
9183         if (WhiteOnMove(forwardMostMove)) {
9184             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9185         } else {
9186             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9187         }
9188         return;
9189     } else if (strstr(message, "Draw") != NULL ||
9190                strstr(message, "game is a draw") != NULL) {
9191         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9192         return;
9193     } else if (strstr(message, "offer") != NULL &&
9194                strstr(message, "draw") != NULL) {
9195 #if ZIPPY
9196         if (appData.zippyPlay && first.initDone) {
9197             /* Relay offer to ICS */
9198             SendToICS(ics_prefix);
9199             SendToICS("draw\n");
9200         }
9201 #endif
9202         cps->offeredDraw = 2; /* valid until this engine moves twice */
9203         if (gameMode == TwoMachinesPlay) {
9204             if (cps->other->offeredDraw) {
9205                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9206             /* [HGM] in two-machine mode we delay relaying draw offer      */
9207             /* until after we also have move, to see if it is really claim */
9208             }
9209         } else if (gameMode == MachinePlaysWhite ||
9210                    gameMode == MachinePlaysBlack) {
9211           if (userOfferedDraw) {
9212             DisplayInformation(_("Machine accepts your draw offer"));
9213             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9214           } else {
9215             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9216           }
9217         }
9218     }
9219
9220
9221     /*
9222      * Look for thinking output
9223      */
9224     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9225           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9226                                 ) {
9227         int plylev, mvleft, mvtot, curscore, time;
9228         char mvname[MOVE_LEN];
9229         u64 nodes; // [DM]
9230         char plyext;
9231         int ignore = FALSE;
9232         int prefixHint = FALSE;
9233         mvname[0] = NULLCHAR;
9234
9235         switch (gameMode) {
9236           case MachinePlaysBlack:
9237           case IcsPlayingBlack:
9238             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9239             break;
9240           case MachinePlaysWhite:
9241           case IcsPlayingWhite:
9242             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9243             break;
9244           case AnalyzeMode:
9245           case AnalyzeFile:
9246             break;
9247           case IcsObserving: /* [DM] icsEngineAnalyze */
9248             if (!appData.icsEngineAnalyze) ignore = TRUE;
9249             break;
9250           case TwoMachinesPlay:
9251             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9252                 ignore = TRUE;
9253             }
9254             break;
9255           default:
9256             ignore = TRUE;
9257             break;
9258         }
9259
9260         if (!ignore) {
9261             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9262             buf1[0] = NULLCHAR;
9263             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9264                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9265
9266                 if (plyext != ' ' && plyext != '\t') {
9267                     time *= 100;
9268                 }
9269
9270                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9271                 if( cps->scoreIsAbsolute &&
9272                     ( gameMode == MachinePlaysBlack ||
9273                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9274                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9275                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9276                      !WhiteOnMove(currentMove)
9277                     ) )
9278                 {
9279                     curscore = -curscore;
9280                 }
9281
9282                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9283
9284                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9285                         char buf[MSG_SIZ];
9286                         FILE *f;
9287                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9288                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9289                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9290                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9291                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9292                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9293                                 fclose(f);
9294                         }
9295                         else
9296                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9297                           DisplayError(_("failed writing PV"), 0);
9298                 }
9299
9300                 tempStats.depth = plylev;
9301                 tempStats.nodes = nodes;
9302                 tempStats.time = time;
9303                 tempStats.score = curscore;
9304                 tempStats.got_only_move = 0;
9305
9306                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9307                         int ticklen;
9308
9309                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9310                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9311                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9312                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9313                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9314                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9315                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9316                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9317                 }
9318
9319                 /* Buffer overflow protection */
9320                 if (pv[0] != NULLCHAR) {
9321                     if (strlen(pv) >= sizeof(tempStats.movelist)
9322                         && appData.debugMode) {
9323                         fprintf(debugFP,
9324                                 "PV is too long; using the first %u bytes.\n",
9325                                 (unsigned) sizeof(tempStats.movelist) - 1);
9326                     }
9327
9328                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9329                 } else {
9330                     sprintf(tempStats.movelist, " no PV\n");
9331                 }
9332
9333                 if (tempStats.seen_stat) {
9334                     tempStats.ok_to_send = 1;
9335                 }
9336
9337                 if (strchr(tempStats.movelist, '(') != NULL) {
9338                     tempStats.line_is_book = 1;
9339                     tempStats.nr_moves = 0;
9340                     tempStats.moves_left = 0;
9341                 } else {
9342                     tempStats.line_is_book = 0;
9343                 }
9344
9345                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9346                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9347
9348                 SendProgramStatsToFrontend( cps, &tempStats );
9349
9350                 /*
9351                     [AS] Protect the thinkOutput buffer from overflow... this
9352                     is only useful if buf1 hasn't overflowed first!
9353                 */
9354                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9355                          plylev,
9356                          (gameMode == TwoMachinesPlay ?
9357                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9358                          ((double) curscore) / 100.0,
9359                          prefixHint ? lastHint : "",
9360                          prefixHint ? " " : "" );
9361
9362                 if( buf1[0] != NULLCHAR ) {
9363                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9364
9365                     if( strlen(pv) > max_len ) {
9366                         if( appData.debugMode) {
9367                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9368                         }
9369                         pv[max_len+1] = '\0';
9370                     }
9371
9372                     strcat( thinkOutput, pv);
9373                 }
9374
9375                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9376                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9377                     DisplayMove(currentMove - 1);
9378                 }
9379                 return;
9380
9381             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9382                 /* crafty (9.25+) says "(only move) <move>"
9383                  * if there is only 1 legal move
9384                  */
9385                 sscanf(p, "(only move) %s", buf1);
9386                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9387                 sprintf(programStats.movelist, "%s (only move)", buf1);
9388                 programStats.depth = 1;
9389                 programStats.nr_moves = 1;
9390                 programStats.moves_left = 1;
9391                 programStats.nodes = 1;
9392                 programStats.time = 1;
9393                 programStats.got_only_move = 1;
9394
9395                 /* Not really, but we also use this member to
9396                    mean "line isn't going to change" (Crafty
9397                    isn't searching, so stats won't change) */
9398                 programStats.line_is_book = 1;
9399
9400                 SendProgramStatsToFrontend( cps, &programStats );
9401
9402                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9403                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9404                     DisplayMove(currentMove - 1);
9405                 }
9406                 return;
9407             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9408                               &time, &nodes, &plylev, &mvleft,
9409                               &mvtot, mvname) >= 5) {
9410                 /* The stat01: line is from Crafty (9.29+) in response
9411                    to the "." command */
9412                 programStats.seen_stat = 1;
9413                 cps->maybeThinking = TRUE;
9414
9415                 if (programStats.got_only_move || !appData.periodicUpdates)
9416                   return;
9417
9418                 programStats.depth = plylev;
9419                 programStats.time = time;
9420                 programStats.nodes = nodes;
9421                 programStats.moves_left = mvleft;
9422                 programStats.nr_moves = mvtot;
9423                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9424                 programStats.ok_to_send = 1;
9425                 programStats.movelist[0] = '\0';
9426
9427                 SendProgramStatsToFrontend( cps, &programStats );
9428
9429                 return;
9430
9431             } else if (strncmp(message,"++",2) == 0) {
9432                 /* Crafty 9.29+ outputs this */
9433                 programStats.got_fail = 2;
9434                 return;
9435
9436             } else if (strncmp(message,"--",2) == 0) {
9437                 /* Crafty 9.29+ outputs this */
9438                 programStats.got_fail = 1;
9439                 return;
9440
9441             } else if (thinkOutput[0] != NULLCHAR &&
9442                        strncmp(message, "    ", 4) == 0) {
9443                 unsigned message_len;
9444
9445                 p = message;
9446                 while (*p && *p == ' ') p++;
9447
9448                 message_len = strlen( p );
9449
9450                 /* [AS] Avoid buffer overflow */
9451                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9452                     strcat(thinkOutput, " ");
9453                     strcat(thinkOutput, p);
9454                 }
9455
9456                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9457                     strcat(programStats.movelist, " ");
9458                     strcat(programStats.movelist, p);
9459                 }
9460
9461                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9462                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9463                     DisplayMove(currentMove - 1);
9464                 }
9465                 return;
9466             }
9467         }
9468         else {
9469             buf1[0] = NULLCHAR;
9470
9471             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9472                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9473             {
9474                 ChessProgramStats cpstats;
9475
9476                 if (plyext != ' ' && plyext != '\t') {
9477                     time *= 100;
9478                 }
9479
9480                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9481                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9482                     curscore = -curscore;
9483                 }
9484
9485                 cpstats.depth = plylev;
9486                 cpstats.nodes = nodes;
9487                 cpstats.time = time;
9488                 cpstats.score = curscore;
9489                 cpstats.got_only_move = 0;
9490                 cpstats.movelist[0] = '\0';
9491
9492                 if (buf1[0] != NULLCHAR) {
9493                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9494                 }
9495
9496                 cpstats.ok_to_send = 0;
9497                 cpstats.line_is_book = 0;
9498                 cpstats.nr_moves = 0;
9499                 cpstats.moves_left = 0;
9500
9501                 SendProgramStatsToFrontend( cps, &cpstats );
9502             }
9503         }
9504     }
9505 }
9506
9507
9508 /* Parse a game score from the character string "game", and
9509    record it as the history of the current game.  The game
9510    score is NOT assumed to start from the standard position.
9511    The display is not updated in any way.
9512    */
9513 void
9514 ParseGameHistory (char *game)
9515 {
9516     ChessMove moveType;
9517     int fromX, fromY, toX, toY, boardIndex;
9518     char promoChar;
9519     char *p, *q;
9520     char buf[MSG_SIZ];
9521
9522     if (appData.debugMode)
9523       fprintf(debugFP, "Parsing game history: %s\n", game);
9524
9525     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9526     gameInfo.site = StrSave(appData.icsHost);
9527     gameInfo.date = PGNDate();
9528     gameInfo.round = StrSave("-");
9529
9530     /* Parse out names of players */
9531     while (*game == ' ') game++;
9532     p = buf;
9533     while (*game != ' ') *p++ = *game++;
9534     *p = NULLCHAR;
9535     gameInfo.white = StrSave(buf);
9536     while (*game == ' ') game++;
9537     p = buf;
9538     while (*game != ' ' && *game != '\n') *p++ = *game++;
9539     *p = NULLCHAR;
9540     gameInfo.black = StrSave(buf);
9541
9542     /* Parse moves */
9543     boardIndex = blackPlaysFirst ? 1 : 0;
9544     yynewstr(game);
9545     for (;;) {
9546         yyboardindex = boardIndex;
9547         moveType = (ChessMove) Myylex();
9548         switch (moveType) {
9549           case IllegalMove:             /* maybe suicide chess, etc. */
9550   if (appData.debugMode) {
9551     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9552     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9553     setbuf(debugFP, NULL);
9554   }
9555           case WhitePromotion:
9556           case BlackPromotion:
9557           case WhiteNonPromotion:
9558           case BlackNonPromotion:
9559           case NormalMove:
9560           case FirstLeg:
9561           case WhiteCapturesEnPassant:
9562           case BlackCapturesEnPassant:
9563           case WhiteKingSideCastle:
9564           case WhiteQueenSideCastle:
9565           case BlackKingSideCastle:
9566           case BlackQueenSideCastle:
9567           case WhiteKingSideCastleWild:
9568           case WhiteQueenSideCastleWild:
9569           case BlackKingSideCastleWild:
9570           case BlackQueenSideCastleWild:
9571           /* PUSH Fabien */
9572           case WhiteHSideCastleFR:
9573           case WhiteASideCastleFR:
9574           case BlackHSideCastleFR:
9575           case BlackASideCastleFR:
9576           /* POP Fabien */
9577             fromX = currentMoveString[0] - AAA;
9578             fromY = currentMoveString[1] - ONE;
9579             toX = currentMoveString[2] - AAA;
9580             toY = currentMoveString[3] - ONE;
9581             promoChar = currentMoveString[4];
9582             break;
9583           case WhiteDrop:
9584           case BlackDrop:
9585             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9586             fromX = moveType == WhiteDrop ?
9587               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9588             (int) CharToPiece(ToLower(currentMoveString[0]));
9589             fromY = DROP_RANK;
9590             toX = currentMoveString[2] - AAA;
9591             toY = currentMoveString[3] - ONE;
9592             promoChar = NULLCHAR;
9593             break;
9594           case AmbiguousMove:
9595             /* bug? */
9596             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9597   if (appData.debugMode) {
9598     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9599     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9600     setbuf(debugFP, NULL);
9601   }
9602             DisplayError(buf, 0);
9603             return;
9604           case ImpossibleMove:
9605             /* bug? */
9606             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9607   if (appData.debugMode) {
9608     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9609     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9610     setbuf(debugFP, NULL);
9611   }
9612             DisplayError(buf, 0);
9613             return;
9614           case EndOfFile:
9615             if (boardIndex < backwardMostMove) {
9616                 /* Oops, gap.  How did that happen? */
9617                 DisplayError(_("Gap in move list"), 0);
9618                 return;
9619             }
9620             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9621             if (boardIndex > forwardMostMove) {
9622                 forwardMostMove = boardIndex;
9623             }
9624             return;
9625           case ElapsedTime:
9626             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9627                 strcat(parseList[boardIndex-1], " ");
9628                 strcat(parseList[boardIndex-1], yy_text);
9629             }
9630             continue;
9631           case Comment:
9632           case PGNTag:
9633           case NAG:
9634           default:
9635             /* ignore */
9636             continue;
9637           case WhiteWins:
9638           case BlackWins:
9639           case GameIsDrawn:
9640           case GameUnfinished:
9641             if (gameMode == IcsExamining) {
9642                 if (boardIndex < backwardMostMove) {
9643                     /* Oops, gap.  How did that happen? */
9644                     return;
9645                 }
9646                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9647                 return;
9648             }
9649             gameInfo.result = moveType;
9650             p = strchr(yy_text, '{');
9651             if (p == NULL) p = strchr(yy_text, '(');
9652             if (p == NULL) {
9653                 p = yy_text;
9654                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9655             } else {
9656                 q = strchr(p, *p == '{' ? '}' : ')');
9657                 if (q != NULL) *q = NULLCHAR;
9658                 p++;
9659             }
9660             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9661             gameInfo.resultDetails = StrSave(p);
9662             continue;
9663         }
9664         if (boardIndex >= forwardMostMove &&
9665             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9666             backwardMostMove = blackPlaysFirst ? 1 : 0;
9667             return;
9668         }
9669         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9670                                  fromY, fromX, toY, toX, promoChar,
9671                                  parseList[boardIndex]);
9672         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9673         /* currentMoveString is set as a side-effect of yylex */
9674         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9675         strcat(moveList[boardIndex], "\n");
9676         boardIndex++;
9677         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9678         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9679           case MT_NONE:
9680           case MT_STALEMATE:
9681           default:
9682             break;
9683           case MT_CHECK:
9684             if(gameInfo.variant != VariantShogi)
9685                 strcat(parseList[boardIndex - 1], "+");
9686             break;
9687           case MT_CHECKMATE:
9688           case MT_STAINMATE:
9689             strcat(parseList[boardIndex - 1], "#");
9690             break;
9691         }
9692     }
9693 }
9694
9695
9696 /* Apply a move to the given board  */
9697 void
9698 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9699 {
9700   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9701   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9702
9703     /* [HGM] compute & store e.p. status and castling rights for new position */
9704     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9705
9706       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9707       oldEP = (signed char)board[EP_STATUS];
9708       board[EP_STATUS] = EP_NONE;
9709
9710   if (fromY == DROP_RANK) {
9711         /* must be first */
9712         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9713             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9714             return;
9715         }
9716         piece = board[toY][toX] = (ChessSquare) fromX;
9717   } else {
9718       ChessSquare victim;
9719       int i;
9720
9721       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9722            victim = board[killY][killX],
9723            board[killY][killX] = EmptySquare,
9724            board[EP_STATUS] = EP_CAPTURE;
9725
9726       if( board[toY][toX] != EmptySquare ) {
9727            board[EP_STATUS] = EP_CAPTURE;
9728            if( (fromX != toX || fromY != toY) && // not igui!
9729                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9730                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9731                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9732            }
9733       }
9734
9735       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9736            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9737                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9738       } else
9739       if( board[fromY][fromX] == WhitePawn ) {
9740            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9741                board[EP_STATUS] = EP_PAWN_MOVE;
9742            if( toY-fromY==2) {
9743                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9744                         gameInfo.variant != VariantBerolina || toX < fromX)
9745                       board[EP_STATUS] = toX | berolina;
9746                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9747                         gameInfo.variant != VariantBerolina || toX > fromX)
9748                       board[EP_STATUS] = toX;
9749            }
9750       } else
9751       if( board[fromY][fromX] == BlackPawn ) {
9752            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9753                board[EP_STATUS] = EP_PAWN_MOVE;
9754            if( toY-fromY== -2) {
9755                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9756                         gameInfo.variant != VariantBerolina || toX < fromX)
9757                       board[EP_STATUS] = toX | berolina;
9758                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9759                         gameInfo.variant != VariantBerolina || toX > fromX)
9760                       board[EP_STATUS] = toX;
9761            }
9762        }
9763
9764        for(i=0; i<nrCastlingRights; i++) {
9765            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9766               board[CASTLING][i] == toX   && castlingRank[i] == toY
9767              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9768        }
9769
9770        if(gameInfo.variant == VariantSChess) { // update virginity
9771            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9772            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9773            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9774            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9775        }
9776
9777      if (fromX == toX && fromY == toY) return;
9778
9779      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9780      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9781      if(gameInfo.variant == VariantKnightmate)
9782          king += (int) WhiteUnicorn - (int) WhiteKing;
9783
9784     /* Code added by Tord: */
9785     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9786     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9787         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9788       board[fromY][fromX] = EmptySquare;
9789       board[toY][toX] = EmptySquare;
9790       if((toX > fromX) != (piece == WhiteRook)) {
9791         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9792       } else {
9793         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9794       }
9795     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9796                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9797       board[fromY][fromX] = EmptySquare;
9798       board[toY][toX] = EmptySquare;
9799       if((toX > fromX) != (piece == BlackRook)) {
9800         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9801       } else {
9802         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9803       }
9804     /* End of code added by Tord */
9805
9806     } else if (board[fromY][fromX] == king
9807         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9808         && toY == fromY && toX > fromX+1) {
9809         board[fromY][fromX] = EmptySquare;
9810         board[toY][toX] = king;
9811         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9812         board[fromY][BOARD_RGHT-1] = EmptySquare;
9813     } else if (board[fromY][fromX] == king
9814         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9815                && toY == fromY && toX < fromX-1) {
9816         board[fromY][fromX] = EmptySquare;
9817         board[toY][toX] = king;
9818         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9819         board[fromY][BOARD_LEFT] = EmptySquare;
9820     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9821                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9822                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9823                ) {
9824         /* white pawn promotion */
9825         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9826         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9827             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9828         board[fromY][fromX] = EmptySquare;
9829     } else if ((fromY >= BOARD_HEIGHT>>1)
9830                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9831                && (toX != fromX)
9832                && gameInfo.variant != VariantXiangqi
9833                && gameInfo.variant != VariantBerolina
9834                && (board[fromY][fromX] == WhitePawn)
9835                && (board[toY][toX] == EmptySquare)) {
9836         board[fromY][fromX] = EmptySquare;
9837         board[toY][toX] = WhitePawn;
9838         captured = board[toY - 1][toX];
9839         board[toY - 1][toX] = EmptySquare;
9840     } else if ((fromY == BOARD_HEIGHT-4)
9841                && (toX == fromX)
9842                && gameInfo.variant == VariantBerolina
9843                && (board[fromY][fromX] == WhitePawn)
9844                && (board[toY][toX] == EmptySquare)) {
9845         board[fromY][fromX] = EmptySquare;
9846         board[toY][toX] = WhitePawn;
9847         if(oldEP & EP_BEROLIN_A) {
9848                 captured = board[fromY][fromX-1];
9849                 board[fromY][fromX-1] = EmptySquare;
9850         }else{  captured = board[fromY][fromX+1];
9851                 board[fromY][fromX+1] = EmptySquare;
9852         }
9853     } else if (board[fromY][fromX] == king
9854         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9855                && toY == fromY && toX > fromX+1) {
9856         board[fromY][fromX] = EmptySquare;
9857         board[toY][toX] = king;
9858         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9859         board[fromY][BOARD_RGHT-1] = EmptySquare;
9860     } else if (board[fromY][fromX] == king
9861         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9862                && toY == fromY && toX < fromX-1) {
9863         board[fromY][fromX] = EmptySquare;
9864         board[toY][toX] = king;
9865         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9866         board[fromY][BOARD_LEFT] = EmptySquare;
9867     } else if (fromY == 7 && fromX == 3
9868                && board[fromY][fromX] == BlackKing
9869                && toY == 7 && toX == 5) {
9870         board[fromY][fromX] = EmptySquare;
9871         board[toY][toX] = BlackKing;
9872         board[fromY][7] = EmptySquare;
9873         board[toY][4] = BlackRook;
9874     } else if (fromY == 7 && fromX == 3
9875                && board[fromY][fromX] == BlackKing
9876                && toY == 7 && toX == 1) {
9877         board[fromY][fromX] = EmptySquare;
9878         board[toY][toX] = BlackKing;
9879         board[fromY][0] = EmptySquare;
9880         board[toY][2] = BlackRook;
9881     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9882                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9883                && toY < promoRank && promoChar
9884                ) {
9885         /* black pawn promotion */
9886         board[toY][toX] = CharToPiece(ToLower(promoChar));
9887         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9888             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9889         board[fromY][fromX] = EmptySquare;
9890     } else if ((fromY < BOARD_HEIGHT>>1)
9891                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9892                && (toX != fromX)
9893                && gameInfo.variant != VariantXiangqi
9894                && gameInfo.variant != VariantBerolina
9895                && (board[fromY][fromX] == BlackPawn)
9896                && (board[toY][toX] == EmptySquare)) {
9897         board[fromY][fromX] = EmptySquare;
9898         board[toY][toX] = BlackPawn;
9899         captured = board[toY + 1][toX];
9900         board[toY + 1][toX] = EmptySquare;
9901     } else if ((fromY == 3)
9902                && (toX == fromX)
9903                && gameInfo.variant == VariantBerolina
9904                && (board[fromY][fromX] == BlackPawn)
9905                && (board[toY][toX] == EmptySquare)) {
9906         board[fromY][fromX] = EmptySquare;
9907         board[toY][toX] = BlackPawn;
9908         if(oldEP & EP_BEROLIN_A) {
9909                 captured = board[fromY][fromX-1];
9910                 board[fromY][fromX-1] = EmptySquare;
9911         }else{  captured = board[fromY][fromX+1];
9912                 board[fromY][fromX+1] = EmptySquare;
9913         }
9914     } else {
9915         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9916         board[fromY][fromX] = EmptySquare;
9917         board[toY][toX] = piece;
9918     }
9919   }
9920
9921     if (gameInfo.holdingsWidth != 0) {
9922
9923       /* !!A lot more code needs to be written to support holdings  */
9924       /* [HGM] OK, so I have written it. Holdings are stored in the */
9925       /* penultimate board files, so they are automaticlly stored   */
9926       /* in the game history.                                       */
9927       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9928                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9929         /* Delete from holdings, by decreasing count */
9930         /* and erasing image if necessary            */
9931         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9932         if(p < (int) BlackPawn) { /* white drop */
9933              p -= (int)WhitePawn;
9934                  p = PieceToNumber((ChessSquare)p);
9935              if(p >= gameInfo.holdingsSize) p = 0;
9936              if(--board[p][BOARD_WIDTH-2] <= 0)
9937                   board[p][BOARD_WIDTH-1] = EmptySquare;
9938              if((int)board[p][BOARD_WIDTH-2] < 0)
9939                         board[p][BOARD_WIDTH-2] = 0;
9940         } else {                  /* black drop */
9941              p -= (int)BlackPawn;
9942                  p = PieceToNumber((ChessSquare)p);
9943              if(p >= gameInfo.holdingsSize) p = 0;
9944              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9945                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9946              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9947                         board[BOARD_HEIGHT-1-p][1] = 0;
9948         }
9949       }
9950       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9951           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9952         /* [HGM] holdings: Add to holdings, if holdings exist */
9953         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9954                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9955                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9956         }
9957         p = (int) captured;
9958         if (p >= (int) BlackPawn) {
9959           p -= (int)BlackPawn;
9960           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9961                   /* in Shogi restore piece to its original  first */
9962                   captured = (ChessSquare) (DEMOTED captured);
9963                   p = DEMOTED p;
9964           }
9965           p = PieceToNumber((ChessSquare)p);
9966           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9967           board[p][BOARD_WIDTH-2]++;
9968           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9969         } else {
9970           p -= (int)WhitePawn;
9971           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9972                   captured = (ChessSquare) (DEMOTED captured);
9973                   p = DEMOTED p;
9974           }
9975           p = PieceToNumber((ChessSquare)p);
9976           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9977           board[BOARD_HEIGHT-1-p][1]++;
9978           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9979         }
9980       }
9981     } else if (gameInfo.variant == VariantAtomic) {
9982       if (captured != EmptySquare) {
9983         int y, x;
9984         for (y = toY-1; y <= toY+1; y++) {
9985           for (x = toX-1; x <= toX+1; x++) {
9986             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9987                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9988               board[y][x] = EmptySquare;
9989             }
9990           }
9991         }
9992         board[toY][toX] = EmptySquare;
9993       }
9994     }
9995     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9996         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9997     } else
9998     if(promoChar == '+') {
9999         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10000         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10001         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10002           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10003     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10004         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10005         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10006            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10007         board[toY][toX] = newPiece;
10008     }
10009     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10010                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10011         // [HGM] superchess: take promotion piece out of holdings
10012         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10013         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10014             if(!--board[k][BOARD_WIDTH-2])
10015                 board[k][BOARD_WIDTH-1] = EmptySquare;
10016         } else {
10017             if(!--board[BOARD_HEIGHT-1-k][1])
10018                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10019         }
10020     }
10021 }
10022
10023 /* Updates forwardMostMove */
10024 void
10025 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10026 {
10027     int x = toX, y = toY;
10028     char *s = parseList[forwardMostMove];
10029     ChessSquare p = boards[forwardMostMove][toY][toX];
10030 //    forwardMostMove++; // [HGM] bare: moved downstream
10031
10032     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10033     (void) CoordsToAlgebraic(boards[forwardMostMove],
10034                              PosFlags(forwardMostMove),
10035                              fromY, fromX, y, x, promoChar,
10036                              s);
10037     if(killX >= 0 && killY >= 0)
10038         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10039
10040     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10041         int timeLeft; static int lastLoadFlag=0; int king, piece;
10042         piece = boards[forwardMostMove][fromY][fromX];
10043         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10044         if(gameInfo.variant == VariantKnightmate)
10045             king += (int) WhiteUnicorn - (int) WhiteKing;
10046         if(forwardMostMove == 0) {
10047             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10048                 fprintf(serverMoves, "%s;", UserName());
10049             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10050                 fprintf(serverMoves, "%s;", second.tidy);
10051             fprintf(serverMoves, "%s;", first.tidy);
10052             if(gameMode == MachinePlaysWhite)
10053                 fprintf(serverMoves, "%s;", UserName());
10054             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10055                 fprintf(serverMoves, "%s;", second.tidy);
10056         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10057         lastLoadFlag = loadFlag;
10058         // print base move
10059         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10060         // print castling suffix
10061         if( toY == fromY && piece == king ) {
10062             if(toX-fromX > 1)
10063                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10064             if(fromX-toX >1)
10065                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10066         }
10067         // e.p. suffix
10068         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10069              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10070              boards[forwardMostMove][toY][toX] == EmptySquare
10071              && fromX != toX && fromY != toY)
10072                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10073         // promotion suffix
10074         if(promoChar != NULLCHAR) {
10075             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10076                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10077                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10078             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10079         }
10080         if(!loadFlag) {
10081                 char buf[MOVE_LEN*2], *p; int len;
10082             fprintf(serverMoves, "/%d/%d",
10083                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10084             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10085             else                      timeLeft = blackTimeRemaining/1000;
10086             fprintf(serverMoves, "/%d", timeLeft);
10087                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10088                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10089                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10090                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10091             fprintf(serverMoves, "/%s", buf);
10092         }
10093         fflush(serverMoves);
10094     }
10095
10096     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10097         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10098       return;
10099     }
10100     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10101     if (commentList[forwardMostMove+1] != NULL) {
10102         free(commentList[forwardMostMove+1]);
10103         commentList[forwardMostMove+1] = NULL;
10104     }
10105     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10106     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10107     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10108     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10109     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10110     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10111     adjustedClock = FALSE;
10112     gameInfo.result = GameUnfinished;
10113     if (gameInfo.resultDetails != NULL) {
10114         free(gameInfo.resultDetails);
10115         gameInfo.resultDetails = NULL;
10116     }
10117     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10118                               moveList[forwardMostMove - 1]);
10119     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10120       case MT_NONE:
10121       case MT_STALEMATE:
10122       default:
10123         break;
10124       case MT_CHECK:
10125         if(gameInfo.variant != VariantShogi)
10126             strcat(parseList[forwardMostMove - 1], "+");
10127         break;
10128       case MT_CHECKMATE:
10129       case MT_STAINMATE:
10130         strcat(parseList[forwardMostMove - 1], "#");
10131         break;
10132     }
10133 }
10134
10135 /* Updates currentMove if not pausing */
10136 void
10137 ShowMove (int fromX, int fromY, int toX, int toY)
10138 {
10139     int instant = (gameMode == PlayFromGameFile) ?
10140         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10141     if(appData.noGUI) return;
10142     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10143         if (!instant) {
10144             if (forwardMostMove == currentMove + 1) {
10145                 AnimateMove(boards[forwardMostMove - 1],
10146                             fromX, fromY, toX, toY);
10147             }
10148         }
10149         currentMove = forwardMostMove;
10150     }
10151
10152     killX = killY = -1; // [HGM] lion: used up
10153
10154     if (instant) return;
10155
10156     DisplayMove(currentMove - 1);
10157     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10158             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10159                 SetHighlights(fromX, fromY, toX, toY);
10160             }
10161     }
10162     DrawPosition(FALSE, boards[currentMove]);
10163     DisplayBothClocks();
10164     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10165 }
10166
10167 void
10168 SendEgtPath (ChessProgramState *cps)
10169 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10170         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10171
10172         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10173
10174         while(*p) {
10175             char c, *q = name+1, *r, *s;
10176
10177             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10178             while(*p && *p != ',') *q++ = *p++;
10179             *q++ = ':'; *q = 0;
10180             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10181                 strcmp(name, ",nalimov:") == 0 ) {
10182                 // take nalimov path from the menu-changeable option first, if it is defined
10183               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10184                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10185             } else
10186             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10187                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10188                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10189                 s = r = StrStr(s, ":") + 1; // beginning of path info
10190                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10191                 c = *r; *r = 0;             // temporarily null-terminate path info
10192                     *--q = 0;               // strip of trailig ':' from name
10193                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10194                 *r = c;
10195                 SendToProgram(buf,cps);     // send egtbpath command for this format
10196             }
10197             if(*p == ',') p++; // read away comma to position for next format name
10198         }
10199 }
10200
10201 static int
10202 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10203 {
10204       int width = 8, height = 8, holdings = 0;             // most common sizes
10205       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10206       // correct the deviations default for each variant
10207       if( v == VariantXiangqi ) width = 9,  height = 10;
10208       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10209       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10210       if( v == VariantCapablanca || v == VariantCapaRandom ||
10211           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10212                                 width = 10;
10213       if( v == VariantCourier ) width = 12;
10214       if( v == VariantSuper )                            holdings = 8;
10215       if( v == VariantGreat )   width = 10,              holdings = 8;
10216       if( v == VariantSChess )                           holdings = 7;
10217       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10218       if( v == VariantChuChess) width = 10, height = 10;
10219       if( v == VariantChu )     width = 12, height = 12;
10220       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10221              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10222              holdingsSize >= 0 && holdingsSize != holdings;
10223 }
10224
10225 char variantError[MSG_SIZ];
10226
10227 char *
10228 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10229 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10230       char *p, *variant = VariantName(v);
10231       static char b[MSG_SIZ];
10232       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10233            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10234                                                holdingsSize, variant); // cook up sized variant name
10235            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10236            if(StrStr(list, b) == NULL) {
10237                // specific sized variant not known, check if general sizing allowed
10238                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10239                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10240                             boardWidth, boardHeight, holdingsSize, engine);
10241                    return NULL;
10242                }
10243                /* [HGM] here we really should compare with the maximum supported board size */
10244            }
10245       } else snprintf(b, MSG_SIZ,"%s", variant);
10246       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10247       p = StrStr(list, b);
10248       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10249       if(p == NULL) {
10250           // occurs not at all in list, or only as sub-string
10251           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10252           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10253               int l = strlen(variantError);
10254               char *q;
10255               while(p != list && p[-1] != ',') p--;
10256               q = strchr(p, ',');
10257               if(q) *q = NULLCHAR;
10258               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10259               if(q) *q= ',';
10260           }
10261           return NULL;
10262       }
10263       return b;
10264 }
10265
10266 void
10267 InitChessProgram (ChessProgramState *cps, int setup)
10268 /* setup needed to setup FRC opening position */
10269 {
10270     char buf[MSG_SIZ], *b;
10271     if (appData.noChessProgram) return;
10272     hintRequested = FALSE;
10273     bookRequested = FALSE;
10274
10275     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10276     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10277     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10278     if(cps->memSize) { /* [HGM] memory */
10279       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10280         SendToProgram(buf, cps);
10281     }
10282     SendEgtPath(cps); /* [HGM] EGT */
10283     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10284       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10285         SendToProgram(buf, cps);
10286     }
10287
10288     SendToProgram(cps->initString, cps);
10289     if (gameInfo.variant != VariantNormal &&
10290         gameInfo.variant != VariantLoadable
10291         /* [HGM] also send variant if board size non-standard */
10292         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10293
10294       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10295                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10296       if (b == NULL) {
10297         DisplayFatalError(variantError, 0, 1);
10298         return;
10299       }
10300
10301       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10302       SendToProgram(buf, cps);
10303     }
10304     currentlyInitializedVariant = gameInfo.variant;
10305
10306     /* [HGM] send opening position in FRC to first engine */
10307     if(setup) {
10308           SendToProgram("force\n", cps);
10309           SendBoard(cps, 0);
10310           /* engine is now in force mode! Set flag to wake it up after first move. */
10311           setboardSpoiledMachineBlack = 1;
10312     }
10313
10314     if (cps->sendICS) {
10315       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10316       SendToProgram(buf, cps);
10317     }
10318     cps->maybeThinking = FALSE;
10319     cps->offeredDraw = 0;
10320     if (!appData.icsActive) {
10321         SendTimeControl(cps, movesPerSession, timeControl,
10322                         timeIncrement, appData.searchDepth,
10323                         searchTime);
10324     }
10325     if (appData.showThinking
10326         // [HGM] thinking: four options require thinking output to be sent
10327         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10328                                 ) {
10329         SendToProgram("post\n", cps);
10330     }
10331     SendToProgram("hard\n", cps);
10332     if (!appData.ponderNextMove) {
10333         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10334            it without being sure what state we are in first.  "hard"
10335            is not a toggle, so that one is OK.
10336          */
10337         SendToProgram("easy\n", cps);
10338     }
10339     if (cps->usePing) {
10340       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10341       SendToProgram(buf, cps);
10342     }
10343     cps->initDone = TRUE;
10344     ClearEngineOutputPane(cps == &second);
10345 }
10346
10347
10348 void
10349 ResendOptions (ChessProgramState *cps)
10350 { // send the stored value of the options
10351   int i;
10352   char buf[MSG_SIZ];
10353   Option *opt = cps->option;
10354   for(i=0; i<cps->nrOptions; i++, opt++) {
10355       switch(opt->type) {
10356         case Spin:
10357         case Slider:
10358         case CheckBox:
10359             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10360           break;
10361         case ComboBox:
10362           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10363           break;
10364         default:
10365             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10366           break;
10367         case Button:
10368         case SaveButton:
10369           continue;
10370       }
10371       SendToProgram(buf, cps);
10372   }
10373 }
10374
10375 void
10376 StartChessProgram (ChessProgramState *cps)
10377 {
10378     char buf[MSG_SIZ];
10379     int err;
10380
10381     if (appData.noChessProgram) return;
10382     cps->initDone = FALSE;
10383
10384     if (strcmp(cps->host, "localhost") == 0) {
10385         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10386     } else if (*appData.remoteShell == NULLCHAR) {
10387         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10388     } else {
10389         if (*appData.remoteUser == NULLCHAR) {
10390           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10391                     cps->program);
10392         } else {
10393           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10394                     cps->host, appData.remoteUser, cps->program);
10395         }
10396         err = StartChildProcess(buf, "", &cps->pr);
10397     }
10398
10399     if (err != 0) {
10400       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10401         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10402         if(cps != &first) return;
10403         appData.noChessProgram = TRUE;
10404         ThawUI();
10405         SetNCPMode();
10406 //      DisplayFatalError(buf, err, 1);
10407 //      cps->pr = NoProc;
10408 //      cps->isr = NULL;
10409         return;
10410     }
10411
10412     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10413     if (cps->protocolVersion > 1) {
10414       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10415       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10416         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10417         cps->comboCnt = 0;  //                and values of combo boxes
10418       }
10419       SendToProgram(buf, cps);
10420       if(cps->reload) ResendOptions(cps);
10421     } else {
10422       SendToProgram("xboard\n", cps);
10423     }
10424 }
10425
10426 void
10427 TwoMachinesEventIfReady P((void))
10428 {
10429   static int curMess = 0;
10430   if (first.lastPing != first.lastPong) {
10431     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10432     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10433     return;
10434   }
10435   if (second.lastPing != second.lastPong) {
10436     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10437     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10438     return;
10439   }
10440   DisplayMessage("", ""); curMess = 0;
10441   TwoMachinesEvent();
10442 }
10443
10444 char *
10445 MakeName (char *template)
10446 {
10447     time_t clock;
10448     struct tm *tm;
10449     static char buf[MSG_SIZ];
10450     char *p = buf;
10451     int i;
10452
10453     clock = time((time_t *)NULL);
10454     tm = localtime(&clock);
10455
10456     while(*p++ = *template++) if(p[-1] == '%') {
10457         switch(*template++) {
10458           case 0:   *p = 0; return buf;
10459           case 'Y': i = tm->tm_year+1900; break;
10460           case 'y': i = tm->tm_year-100; break;
10461           case 'M': i = tm->tm_mon+1; break;
10462           case 'd': i = tm->tm_mday; break;
10463           case 'h': i = tm->tm_hour; break;
10464           case 'm': i = tm->tm_min; break;
10465           case 's': i = tm->tm_sec; break;
10466           default:  i = 0;
10467         }
10468         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10469     }
10470     return buf;
10471 }
10472
10473 int
10474 CountPlayers (char *p)
10475 {
10476     int n = 0;
10477     while(p = strchr(p, '\n')) p++, n++; // count participants
10478     return n;
10479 }
10480
10481 FILE *
10482 WriteTourneyFile (char *results, FILE *f)
10483 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10484     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10485     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10486         // create a file with tournament description
10487         fprintf(f, "-participants {%s}\n", appData.participants);
10488         fprintf(f, "-seedBase %d\n", appData.seedBase);
10489         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10490         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10491         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10492         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10493         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10494         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10495         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10496         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10497         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10498         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10499         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10500         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10501         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10502         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10503         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10504         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10505         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10506         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10507         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10508         fprintf(f, "-smpCores %d\n", appData.smpCores);
10509         if(searchTime > 0)
10510                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10511         else {
10512                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10513                 fprintf(f, "-tc %s\n", appData.timeControl);
10514                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10515         }
10516         fprintf(f, "-results \"%s\"\n", results);
10517     }
10518     return f;
10519 }
10520
10521 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10522
10523 void
10524 Substitute (char *participants, int expunge)
10525 {
10526     int i, changed, changes=0, nPlayers=0;
10527     char *p, *q, *r, buf[MSG_SIZ];
10528     if(participants == NULL) return;
10529     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10530     r = p = participants; q = appData.participants;
10531     while(*p && *p == *q) {
10532         if(*p == '\n') r = p+1, nPlayers++;
10533         p++; q++;
10534     }
10535     if(*p) { // difference
10536         while(*p && *p++ != '\n');
10537         while(*q && *q++ != '\n');
10538       changed = nPlayers;
10539         changes = 1 + (strcmp(p, q) != 0);
10540     }
10541     if(changes == 1) { // a single engine mnemonic was changed
10542         q = r; while(*q) nPlayers += (*q++ == '\n');
10543         p = buf; while(*r && (*p = *r++) != '\n') p++;
10544         *p = NULLCHAR;
10545         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10546         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10547         if(mnemonic[i]) { // The substitute is valid
10548             FILE *f;
10549             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10550                 flock(fileno(f), LOCK_EX);
10551                 ParseArgsFromFile(f);
10552                 fseek(f, 0, SEEK_SET);
10553                 FREE(appData.participants); appData.participants = participants;
10554                 if(expunge) { // erase results of replaced engine
10555                     int len = strlen(appData.results), w, b, dummy;
10556                     for(i=0; i<len; i++) {
10557                         Pairing(i, nPlayers, &w, &b, &dummy);
10558                         if((w == changed || b == changed) && appData.results[i] == '*') {
10559                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10560                             fclose(f);
10561                             return;
10562                         }
10563                     }
10564                     for(i=0; i<len; i++) {
10565                         Pairing(i, nPlayers, &w, &b, &dummy);
10566                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10567                     }
10568                 }
10569                 WriteTourneyFile(appData.results, f);
10570                 fclose(f); // release lock
10571                 return;
10572             }
10573         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10574     }
10575     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10576     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10577     free(participants);
10578     return;
10579 }
10580
10581 int
10582 CheckPlayers (char *participants)
10583 {
10584         int i;
10585         char buf[MSG_SIZ], *p;
10586         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10587         while(p = strchr(participants, '\n')) {
10588             *p = NULLCHAR;
10589             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10590             if(!mnemonic[i]) {
10591                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10592                 *p = '\n';
10593                 DisplayError(buf, 0);
10594                 return 1;
10595             }
10596             *p = '\n';
10597             participants = p + 1;
10598         }
10599         return 0;
10600 }
10601
10602 int
10603 CreateTourney (char *name)
10604 {
10605         FILE *f;
10606         if(matchMode && strcmp(name, appData.tourneyFile)) {
10607              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10608         }
10609         if(name[0] == NULLCHAR) {
10610             if(appData.participants[0])
10611                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10612             return 0;
10613         }
10614         f = fopen(name, "r");
10615         if(f) { // file exists
10616             ASSIGN(appData.tourneyFile, name);
10617             ParseArgsFromFile(f); // parse it
10618         } else {
10619             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10620             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10621                 DisplayError(_("Not enough participants"), 0);
10622                 return 0;
10623             }
10624             if(CheckPlayers(appData.participants)) return 0;
10625             ASSIGN(appData.tourneyFile, name);
10626             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10627             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10628         }
10629         fclose(f);
10630         appData.noChessProgram = FALSE;
10631         appData.clockMode = TRUE;
10632         SetGNUMode();
10633         return 1;
10634 }
10635
10636 int
10637 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10638 {
10639     char buf[MSG_SIZ], *p, *q;
10640     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10641     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10642     skip = !all && group[0]; // if group requested, we start in skip mode
10643     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10644         p = names; q = buf; header = 0;
10645         while(*p && *p != '\n') *q++ = *p++;
10646         *q = 0;
10647         if(*p == '\n') p++;
10648         if(buf[0] == '#') {
10649             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10650             depth++; // we must be entering a new group
10651             if(all) continue; // suppress printing group headers when complete list requested
10652             header = 1;
10653             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10654         }
10655         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10656         if(engineList[i]) free(engineList[i]);
10657         engineList[i] = strdup(buf);
10658         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10659         if(engineMnemonic[i]) free(engineMnemonic[i]);
10660         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10661             strcat(buf, " (");
10662             sscanf(q + 8, "%s", buf + strlen(buf));
10663             strcat(buf, ")");
10664         }
10665         engineMnemonic[i] = strdup(buf);
10666         i++;
10667     }
10668     engineList[i] = engineMnemonic[i] = NULL;
10669     return i;
10670 }
10671
10672 // following implemented as macro to avoid type limitations
10673 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10674
10675 void
10676 SwapEngines (int n)
10677 {   // swap settings for first engine and other engine (so far only some selected options)
10678     int h;
10679     char *p;
10680     if(n == 0) return;
10681     SWAP(directory, p)
10682     SWAP(chessProgram, p)
10683     SWAP(isUCI, h)
10684     SWAP(hasOwnBookUCI, h)
10685     SWAP(protocolVersion, h)
10686     SWAP(reuse, h)
10687     SWAP(scoreIsAbsolute, h)
10688     SWAP(timeOdds, h)
10689     SWAP(logo, p)
10690     SWAP(pgnName, p)
10691     SWAP(pvSAN, h)
10692     SWAP(engOptions, p)
10693     SWAP(engInitString, p)
10694     SWAP(computerString, p)
10695     SWAP(features, p)
10696     SWAP(fenOverride, p)
10697     SWAP(NPS, h)
10698     SWAP(accumulateTC, h)
10699     SWAP(host, p)
10700 }
10701
10702 int
10703 GetEngineLine (char *s, int n)
10704 {
10705     int i;
10706     char buf[MSG_SIZ];
10707     extern char *icsNames;
10708     if(!s || !*s) return 0;
10709     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10710     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10711     if(!mnemonic[i]) return 0;
10712     if(n == 11) return 1; // just testing if there was a match
10713     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10714     if(n == 1) SwapEngines(n);
10715     ParseArgsFromString(buf);
10716     if(n == 1) SwapEngines(n);
10717     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10718         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10719         ParseArgsFromString(buf);
10720     }
10721     return 1;
10722 }
10723
10724 int
10725 SetPlayer (int player, char *p)
10726 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10727     int i;
10728     char buf[MSG_SIZ], *engineName;
10729     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10730     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10731     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10732     if(mnemonic[i]) {
10733         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10734         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10735         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10736         ParseArgsFromString(buf);
10737     } else { // no engine with this nickname is installed!
10738         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10739         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10740         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10741         ModeHighlight();
10742         DisplayError(buf, 0);
10743         return 0;
10744     }
10745     free(engineName);
10746     return i;
10747 }
10748
10749 char *recentEngines;
10750
10751 void
10752 RecentEngineEvent (int nr)
10753 {
10754     int n;
10755 //    SwapEngines(1); // bump first to second
10756 //    ReplaceEngine(&second, 1); // and load it there
10757     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10758     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10759     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10760         ReplaceEngine(&first, 0);
10761         FloatToFront(&appData.recentEngineList, command[n]);
10762     }
10763 }
10764
10765 int
10766 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10767 {   // determine players from game number
10768     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10769
10770     if(appData.tourneyType == 0) {
10771         roundsPerCycle = (nPlayers - 1) | 1;
10772         pairingsPerRound = nPlayers / 2;
10773     } else if(appData.tourneyType > 0) {
10774         roundsPerCycle = nPlayers - appData.tourneyType;
10775         pairingsPerRound = appData.tourneyType;
10776     }
10777     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10778     gamesPerCycle = gamesPerRound * roundsPerCycle;
10779     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10780     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10781     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10782     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10783     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10784     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10785
10786     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10787     if(appData.roundSync) *syncInterval = gamesPerRound;
10788
10789     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10790
10791     if(appData.tourneyType == 0) {
10792         if(curPairing == (nPlayers-1)/2 ) {
10793             *whitePlayer = curRound;
10794             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10795         } else {
10796             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10797             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10798             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10799             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10800         }
10801     } else if(appData.tourneyType > 1) {
10802         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10803         *whitePlayer = curRound + appData.tourneyType;
10804     } else if(appData.tourneyType > 0) {
10805         *whitePlayer = curPairing;
10806         *blackPlayer = curRound + appData.tourneyType;
10807     }
10808
10809     // take care of white/black alternation per round.
10810     // For cycles and games this is already taken care of by default, derived from matchGame!
10811     return curRound & 1;
10812 }
10813
10814 int
10815 NextTourneyGame (int nr, int *swapColors)
10816 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10817     char *p, *q;
10818     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10819     FILE *tf;
10820     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10821     tf = fopen(appData.tourneyFile, "r");
10822     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10823     ParseArgsFromFile(tf); fclose(tf);
10824     InitTimeControls(); // TC might be altered from tourney file
10825
10826     nPlayers = CountPlayers(appData.participants); // count participants
10827     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10828     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10829
10830     if(syncInterval) {
10831         p = q = appData.results;
10832         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10833         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10834             DisplayMessage(_("Waiting for other game(s)"),"");
10835             waitingForGame = TRUE;
10836             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10837             return 0;
10838         }
10839         waitingForGame = FALSE;
10840     }
10841
10842     if(appData.tourneyType < 0) {
10843         if(nr>=0 && !pairingReceived) {
10844             char buf[1<<16];
10845             if(pairing.pr == NoProc) {
10846                 if(!appData.pairingEngine[0]) {
10847                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10848                     return 0;
10849                 }
10850                 StartChessProgram(&pairing); // starts the pairing engine
10851             }
10852             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10853             SendToProgram(buf, &pairing);
10854             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10855             SendToProgram(buf, &pairing);
10856             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10857         }
10858         pairingReceived = 0;                              // ... so we continue here
10859         *swapColors = 0;
10860         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10861         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10862         matchGame = 1; roundNr = nr / syncInterval + 1;
10863     }
10864
10865     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10866
10867     // redefine engines, engine dir, etc.
10868     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10869     if(first.pr == NoProc) {
10870       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10871       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10872     }
10873     if(second.pr == NoProc) {
10874       SwapEngines(1);
10875       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10876       SwapEngines(1);         // and make that valid for second engine by swapping
10877       InitEngine(&second, 1);
10878     }
10879     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10880     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10881     return OK;
10882 }
10883
10884 void
10885 NextMatchGame ()
10886 {   // performs game initialization that does not invoke engines, and then tries to start the game
10887     int res, firstWhite, swapColors = 0;
10888     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10889     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
10890         char buf[MSG_SIZ];
10891         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10892         if(strcmp(buf, currentDebugFile)) { // name has changed
10893             FILE *f = fopen(buf, "w");
10894             if(f) { // if opening the new file failed, just keep using the old one
10895                 ASSIGN(currentDebugFile, buf);
10896                 fclose(debugFP);
10897                 debugFP = f;
10898             }
10899             if(appData.serverFileName) {
10900                 if(serverFP) fclose(serverFP);
10901                 serverFP = fopen(appData.serverFileName, "w");
10902                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10903                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10904             }
10905         }
10906     }
10907     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10908     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10909     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10910     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10911     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10912     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10913     Reset(FALSE, first.pr != NoProc);
10914     res = LoadGameOrPosition(matchGame); // setup game
10915     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10916     if(!res) return; // abort when bad game/pos file
10917     TwoMachinesEvent();
10918 }
10919
10920 void
10921 UserAdjudicationEvent (int result)
10922 {
10923     ChessMove gameResult = GameIsDrawn;
10924
10925     if( result > 0 ) {
10926         gameResult = WhiteWins;
10927     }
10928     else if( result < 0 ) {
10929         gameResult = BlackWins;
10930     }
10931
10932     if( gameMode == TwoMachinesPlay ) {
10933         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10934     }
10935 }
10936
10937
10938 // [HGM] save: calculate checksum of game to make games easily identifiable
10939 int
10940 StringCheckSum (char *s)
10941 {
10942         int i = 0;
10943         if(s==NULL) return 0;
10944         while(*s) i = i*259 + *s++;
10945         return i;
10946 }
10947
10948 int
10949 GameCheckSum ()
10950 {
10951         int i, sum=0;
10952         for(i=backwardMostMove; i<forwardMostMove; i++) {
10953                 sum += pvInfoList[i].depth;
10954                 sum += StringCheckSum(parseList[i]);
10955                 sum += StringCheckSum(commentList[i]);
10956                 sum *= 261;
10957         }
10958         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10959         return sum + StringCheckSum(commentList[i]);
10960 } // end of save patch
10961
10962 void
10963 GameEnds (ChessMove result, char *resultDetails, int whosays)
10964 {
10965     GameMode nextGameMode;
10966     int isIcsGame;
10967     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10968
10969     if(endingGame) return; /* [HGM] crash: forbid recursion */
10970     endingGame = 1;
10971     if(twoBoards) { // [HGM] dual: switch back to one board
10972         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10973         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10974     }
10975     if (appData.debugMode) {
10976       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10977               result, resultDetails ? resultDetails : "(null)", whosays);
10978     }
10979
10980     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10981
10982     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10983
10984     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10985         /* If we are playing on ICS, the server decides when the
10986            game is over, but the engine can offer to draw, claim
10987            a draw, or resign.
10988          */
10989 #if ZIPPY
10990         if (appData.zippyPlay && first.initDone) {
10991             if (result == GameIsDrawn) {
10992                 /* In case draw still needs to be claimed */
10993                 SendToICS(ics_prefix);
10994                 SendToICS("draw\n");
10995             } else if (StrCaseStr(resultDetails, "resign")) {
10996                 SendToICS(ics_prefix);
10997                 SendToICS("resign\n");
10998             }
10999         }
11000 #endif
11001         endingGame = 0; /* [HGM] crash */
11002         return;
11003     }
11004
11005     /* If we're loading the game from a file, stop */
11006     if (whosays == GE_FILE) {
11007       (void) StopLoadGameTimer();
11008       gameFileFP = NULL;
11009     }
11010
11011     /* Cancel draw offers */
11012     first.offeredDraw = second.offeredDraw = 0;
11013
11014     /* If this is an ICS game, only ICS can really say it's done;
11015        if not, anyone can. */
11016     isIcsGame = (gameMode == IcsPlayingWhite ||
11017                  gameMode == IcsPlayingBlack ||
11018                  gameMode == IcsObserving    ||
11019                  gameMode == IcsExamining);
11020
11021     if (!isIcsGame || whosays == GE_ICS) {
11022         /* OK -- not an ICS game, or ICS said it was done */
11023         StopClocks();
11024         if (!isIcsGame && !appData.noChessProgram)
11025           SetUserThinkingEnables();
11026
11027         /* [HGM] if a machine claims the game end we verify this claim */
11028         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11029             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11030                 char claimer;
11031                 ChessMove trueResult = (ChessMove) -1;
11032
11033                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11034                                             first.twoMachinesColor[0] :
11035                                             second.twoMachinesColor[0] ;
11036
11037                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11038                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11039                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11040                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11041                 } else
11042                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11043                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11044                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11045                 } else
11046                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11047                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11048                 }
11049
11050                 // now verify win claims, but not in drop games, as we don't understand those yet
11051                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11052                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11053                     (result == WhiteWins && claimer == 'w' ||
11054                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11055                       if (appData.debugMode) {
11056                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11057                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11058                       }
11059                       if(result != trueResult) {
11060                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11061                               result = claimer == 'w' ? BlackWins : WhiteWins;
11062                               resultDetails = buf;
11063                       }
11064                 } else
11065                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11066                     && (forwardMostMove <= backwardMostMove ||
11067                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11068                         (claimer=='b')==(forwardMostMove&1))
11069                                                                                   ) {
11070                       /* [HGM] verify: draws that were not flagged are false claims */
11071                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11072                       result = claimer == 'w' ? BlackWins : WhiteWins;
11073                       resultDetails = buf;
11074                 }
11075                 /* (Claiming a loss is accepted no questions asked!) */
11076             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11077                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11078                 result = GameUnfinished;
11079                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11080             }
11081             /* [HGM] bare: don't allow bare King to win */
11082             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11083                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11084                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11085                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11086                && result != GameIsDrawn)
11087             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11088                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11089                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11090                         if(p >= 0 && p <= (int)WhiteKing) k++;
11091                 }
11092                 if (appData.debugMode) {
11093                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11094                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11095                 }
11096                 if(k <= 1) {
11097                         result = GameIsDrawn;
11098                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11099                         resultDetails = buf;
11100                 }
11101             }
11102         }
11103
11104
11105         if(serverMoves != NULL && !loadFlag) { char c = '=';
11106             if(result==WhiteWins) c = '+';
11107             if(result==BlackWins) c = '-';
11108             if(resultDetails != NULL)
11109                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11110         }
11111         if (resultDetails != NULL) {
11112             gameInfo.result = result;
11113             gameInfo.resultDetails = StrSave(resultDetails);
11114
11115             /* display last move only if game was not loaded from file */
11116             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11117                 DisplayMove(currentMove - 1);
11118
11119             if (forwardMostMove != 0) {
11120                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11121                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11122                                                                 ) {
11123                     if (*appData.saveGameFile != NULLCHAR) {
11124                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11125                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11126                         else
11127                         SaveGameToFile(appData.saveGameFile, TRUE);
11128                     } else if (appData.autoSaveGames) {
11129                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11130                     }
11131                     if (*appData.savePositionFile != NULLCHAR) {
11132                         SavePositionToFile(appData.savePositionFile);
11133                     }
11134                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11135                 }
11136             }
11137
11138             /* Tell program how game ended in case it is learning */
11139             /* [HGM] Moved this to after saving the PGN, just in case */
11140             /* engine died and we got here through time loss. In that */
11141             /* case we will get a fatal error writing the pipe, which */
11142             /* would otherwise lose us the PGN.                       */
11143             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11144             /* output during GameEnds should never be fatal anymore   */
11145             if (gameMode == MachinePlaysWhite ||
11146                 gameMode == MachinePlaysBlack ||
11147                 gameMode == TwoMachinesPlay ||
11148                 gameMode == IcsPlayingWhite ||
11149                 gameMode == IcsPlayingBlack ||
11150                 gameMode == BeginningOfGame) {
11151                 char buf[MSG_SIZ];
11152                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11153                         resultDetails);
11154                 if (first.pr != NoProc) {
11155                     SendToProgram(buf, &first);
11156                 }
11157                 if (second.pr != NoProc &&
11158                     gameMode == TwoMachinesPlay) {
11159                     SendToProgram(buf, &second);
11160                 }
11161             }
11162         }
11163
11164         if (appData.icsActive) {
11165             if (appData.quietPlay &&
11166                 (gameMode == IcsPlayingWhite ||
11167                  gameMode == IcsPlayingBlack)) {
11168                 SendToICS(ics_prefix);
11169                 SendToICS("set shout 1\n");
11170             }
11171             nextGameMode = IcsIdle;
11172             ics_user_moved = FALSE;
11173             /* clean up premove.  It's ugly when the game has ended and the
11174              * premove highlights are still on the board.
11175              */
11176             if (gotPremove) {
11177               gotPremove = FALSE;
11178               ClearPremoveHighlights();
11179               DrawPosition(FALSE, boards[currentMove]);
11180             }
11181             if (whosays == GE_ICS) {
11182                 switch (result) {
11183                 case WhiteWins:
11184                     if (gameMode == IcsPlayingWhite)
11185                         PlayIcsWinSound();
11186                     else if(gameMode == IcsPlayingBlack)
11187                         PlayIcsLossSound();
11188                     break;
11189                 case BlackWins:
11190                     if (gameMode == IcsPlayingBlack)
11191                         PlayIcsWinSound();
11192                     else if(gameMode == IcsPlayingWhite)
11193                         PlayIcsLossSound();
11194                     break;
11195                 case GameIsDrawn:
11196                     PlayIcsDrawSound();
11197                     break;
11198                 default:
11199                     PlayIcsUnfinishedSound();
11200                 }
11201             }
11202             if(appData.quitNext) { ExitEvent(0); return; }
11203         } else if (gameMode == EditGame ||
11204                    gameMode == PlayFromGameFile ||
11205                    gameMode == AnalyzeMode ||
11206                    gameMode == AnalyzeFile) {
11207             nextGameMode = gameMode;
11208         } else {
11209             nextGameMode = EndOfGame;
11210         }
11211         pausing = FALSE;
11212         ModeHighlight();
11213     } else {
11214         nextGameMode = gameMode;
11215     }
11216
11217     if (appData.noChessProgram) {
11218         gameMode = nextGameMode;
11219         ModeHighlight();
11220         endingGame = 0; /* [HGM] crash */
11221         return;
11222     }
11223
11224     if (first.reuse) {
11225         /* Put first chess program into idle state */
11226         if (first.pr != NoProc &&
11227             (gameMode == MachinePlaysWhite ||
11228              gameMode == MachinePlaysBlack ||
11229              gameMode == TwoMachinesPlay ||
11230              gameMode == IcsPlayingWhite ||
11231              gameMode == IcsPlayingBlack ||
11232              gameMode == BeginningOfGame)) {
11233             SendToProgram("force\n", &first);
11234             if (first.usePing) {
11235               char buf[MSG_SIZ];
11236               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11237               SendToProgram(buf, &first);
11238             }
11239         }
11240     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11241         /* Kill off first chess program */
11242         if (first.isr != NULL)
11243           RemoveInputSource(first.isr);
11244         first.isr = NULL;
11245
11246         if (first.pr != NoProc) {
11247             ExitAnalyzeMode();
11248             DoSleep( appData.delayBeforeQuit );
11249             SendToProgram("quit\n", &first);
11250             DoSleep( appData.delayAfterQuit );
11251             DestroyChildProcess(first.pr, first.useSigterm);
11252             first.reload = TRUE;
11253         }
11254         first.pr = NoProc;
11255     }
11256     if (second.reuse) {
11257         /* Put second chess program into idle state */
11258         if (second.pr != NoProc &&
11259             gameMode == TwoMachinesPlay) {
11260             SendToProgram("force\n", &second);
11261             if (second.usePing) {
11262               char buf[MSG_SIZ];
11263               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11264               SendToProgram(buf, &second);
11265             }
11266         }
11267     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11268         /* Kill off second chess program */
11269         if (second.isr != NULL)
11270           RemoveInputSource(second.isr);
11271         second.isr = NULL;
11272
11273         if (second.pr != NoProc) {
11274             DoSleep( appData.delayBeforeQuit );
11275             SendToProgram("quit\n", &second);
11276             DoSleep( appData.delayAfterQuit );
11277             DestroyChildProcess(second.pr, second.useSigterm);
11278             second.reload = TRUE;
11279         }
11280         second.pr = NoProc;
11281     }
11282
11283     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11284         char resChar = '=';
11285         switch (result) {
11286         case WhiteWins:
11287           resChar = '+';
11288           if (first.twoMachinesColor[0] == 'w') {
11289             first.matchWins++;
11290           } else {
11291             second.matchWins++;
11292           }
11293           break;
11294         case BlackWins:
11295           resChar = '-';
11296           if (first.twoMachinesColor[0] == 'b') {
11297             first.matchWins++;
11298           } else {
11299             second.matchWins++;
11300           }
11301           break;
11302         case GameUnfinished:
11303           resChar = ' ';
11304         default:
11305           break;
11306         }
11307
11308         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11309         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11310             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11311             ReserveGame(nextGame, resChar); // sets nextGame
11312             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11313             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11314         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11315
11316         if (nextGame <= appData.matchGames && !abortMatch) {
11317             gameMode = nextGameMode;
11318             matchGame = nextGame; // this will be overruled in tourney mode!
11319             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11320             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11321             endingGame = 0; /* [HGM] crash */
11322             return;
11323         } else {
11324             gameMode = nextGameMode;
11325             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11326                      first.tidy, second.tidy,
11327                      first.matchWins, second.matchWins,
11328                      appData.matchGames - (first.matchWins + second.matchWins));
11329             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11330             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11331             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11332             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11333                 first.twoMachinesColor = "black\n";
11334                 second.twoMachinesColor = "white\n";
11335             } else {
11336                 first.twoMachinesColor = "white\n";
11337                 second.twoMachinesColor = "black\n";
11338             }
11339         }
11340     }
11341     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11342         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11343       ExitAnalyzeMode();
11344     gameMode = nextGameMode;
11345     ModeHighlight();
11346     endingGame = 0;  /* [HGM] crash */
11347     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11348         if(matchMode == TRUE) { // match through command line: exit with or without popup
11349             if(ranking) {
11350                 ToNrEvent(forwardMostMove);
11351                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11352                 else ExitEvent(0);
11353             } else DisplayFatalError(buf, 0, 0);
11354         } else { // match through menu; just stop, with or without popup
11355             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11356             ModeHighlight();
11357             if(ranking){
11358                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11359             } else DisplayNote(buf);
11360       }
11361       if(ranking) free(ranking);
11362     }
11363 }
11364
11365 /* Assumes program was just initialized (initString sent).
11366    Leaves program in force mode. */
11367 void
11368 FeedMovesToProgram (ChessProgramState *cps, int upto)
11369 {
11370     int i;
11371
11372     if (appData.debugMode)
11373       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11374               startedFromSetupPosition ? "position and " : "",
11375               backwardMostMove, upto, cps->which);
11376     if(currentlyInitializedVariant != gameInfo.variant) {
11377       char buf[MSG_SIZ];
11378         // [HGM] variantswitch: make engine aware of new variant
11379         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11380                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11381                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11382         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11383         SendToProgram(buf, cps);
11384         currentlyInitializedVariant = gameInfo.variant;
11385     }
11386     SendToProgram("force\n", cps);
11387     if (startedFromSetupPosition) {
11388         SendBoard(cps, backwardMostMove);
11389     if (appData.debugMode) {
11390         fprintf(debugFP, "feedMoves\n");
11391     }
11392     }
11393     for (i = backwardMostMove; i < upto; i++) {
11394         SendMoveToProgram(i, cps);
11395     }
11396 }
11397
11398
11399 int
11400 ResurrectChessProgram ()
11401 {
11402      /* The chess program may have exited.
11403         If so, restart it and feed it all the moves made so far. */
11404     static int doInit = 0;
11405
11406     if (appData.noChessProgram) return 1;
11407
11408     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11409         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11410         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11411         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11412     } else {
11413         if (first.pr != NoProc) return 1;
11414         StartChessProgram(&first);
11415     }
11416     InitChessProgram(&first, FALSE);
11417     FeedMovesToProgram(&first, currentMove);
11418
11419     if (!first.sendTime) {
11420         /* can't tell gnuchess what its clock should read,
11421            so we bow to its notion. */
11422         ResetClocks();
11423         timeRemaining[0][currentMove] = whiteTimeRemaining;
11424         timeRemaining[1][currentMove] = blackTimeRemaining;
11425     }
11426
11427     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11428                 appData.icsEngineAnalyze) && first.analysisSupport) {
11429       SendToProgram("analyze\n", &first);
11430       first.analyzing = TRUE;
11431     }
11432     return 1;
11433 }
11434
11435 /*
11436  * Button procedures
11437  */
11438 void
11439 Reset (int redraw, int init)
11440 {
11441     int i;
11442
11443     if (appData.debugMode) {
11444         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11445                 redraw, init, gameMode);
11446     }
11447     CleanupTail(); // [HGM] vari: delete any stored variations
11448     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11449     pausing = pauseExamInvalid = FALSE;
11450     startedFromSetupPosition = blackPlaysFirst = FALSE;
11451     firstMove = TRUE;
11452     whiteFlag = blackFlag = FALSE;
11453     userOfferedDraw = FALSE;
11454     hintRequested = bookRequested = FALSE;
11455     first.maybeThinking = FALSE;
11456     second.maybeThinking = FALSE;
11457     first.bookSuspend = FALSE; // [HGM] book
11458     second.bookSuspend = FALSE;
11459     thinkOutput[0] = NULLCHAR;
11460     lastHint[0] = NULLCHAR;
11461     ClearGameInfo(&gameInfo);
11462     gameInfo.variant = StringToVariant(appData.variant);
11463     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11464     ics_user_moved = ics_clock_paused = FALSE;
11465     ics_getting_history = H_FALSE;
11466     ics_gamenum = -1;
11467     white_holding[0] = black_holding[0] = NULLCHAR;
11468     ClearProgramStats();
11469     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11470
11471     ResetFrontEnd();
11472     ClearHighlights();
11473     flipView = appData.flipView;
11474     ClearPremoveHighlights();
11475     gotPremove = FALSE;
11476     alarmSounded = FALSE;
11477     killX = killY = -1; // [HGM] lion
11478
11479     GameEnds(EndOfFile, NULL, GE_PLAYER);
11480     if(appData.serverMovesName != NULL) {
11481         /* [HGM] prepare to make moves file for broadcasting */
11482         clock_t t = clock();
11483         if(serverMoves != NULL) fclose(serverMoves);
11484         serverMoves = fopen(appData.serverMovesName, "r");
11485         if(serverMoves != NULL) {
11486             fclose(serverMoves);
11487             /* delay 15 sec before overwriting, so all clients can see end */
11488             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11489         }
11490         serverMoves = fopen(appData.serverMovesName, "w");
11491     }
11492
11493     ExitAnalyzeMode();
11494     gameMode = BeginningOfGame;
11495     ModeHighlight();
11496     if(appData.icsActive) gameInfo.variant = VariantNormal;
11497     currentMove = forwardMostMove = backwardMostMove = 0;
11498     MarkTargetSquares(1);
11499     InitPosition(redraw);
11500     for (i = 0; i < MAX_MOVES; i++) {
11501         if (commentList[i] != NULL) {
11502             free(commentList[i]);
11503             commentList[i] = NULL;
11504         }
11505     }
11506     ResetClocks();
11507     timeRemaining[0][0] = whiteTimeRemaining;
11508     timeRemaining[1][0] = blackTimeRemaining;
11509
11510     if (first.pr == NoProc) {
11511         StartChessProgram(&first);
11512     }
11513     if (init) {
11514             InitChessProgram(&first, startedFromSetupPosition);
11515     }
11516     DisplayTitle("");
11517     DisplayMessage("", "");
11518     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11519     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11520     ClearMap();        // [HGM] exclude: invalidate map
11521 }
11522
11523 void
11524 AutoPlayGameLoop ()
11525 {
11526     for (;;) {
11527         if (!AutoPlayOneMove())
11528           return;
11529         if (matchMode || appData.timeDelay == 0)
11530           continue;
11531         if (appData.timeDelay < 0)
11532           return;
11533         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11534         break;
11535     }
11536 }
11537
11538 void
11539 AnalyzeNextGame()
11540 {
11541     ReloadGame(1); // next game
11542 }
11543
11544 int
11545 AutoPlayOneMove ()
11546 {
11547     int fromX, fromY, toX, toY;
11548
11549     if (appData.debugMode) {
11550       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11551     }
11552
11553     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11554       return FALSE;
11555
11556     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11557       pvInfoList[currentMove].depth = programStats.depth;
11558       pvInfoList[currentMove].score = programStats.score;
11559       pvInfoList[currentMove].time  = 0;
11560       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11561       else { // append analysis of final position as comment
11562         char buf[MSG_SIZ];
11563         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11564         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11565       }
11566       programStats.depth = 0;
11567     }
11568
11569     if (currentMove >= forwardMostMove) {
11570       if(gameMode == AnalyzeFile) {
11571           if(appData.loadGameIndex == -1) {
11572             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11573           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11574           } else {
11575           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11576         }
11577       }
11578 //      gameMode = EndOfGame;
11579 //      ModeHighlight();
11580
11581       /* [AS] Clear current move marker at the end of a game */
11582       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11583
11584       return FALSE;
11585     }
11586
11587     toX = moveList[currentMove][2] - AAA;
11588     toY = moveList[currentMove][3] - ONE;
11589
11590     if (moveList[currentMove][1] == '@') {
11591         if (appData.highlightLastMove) {
11592             SetHighlights(-1, -1, toX, toY);
11593         }
11594     } else {
11595         fromX = moveList[currentMove][0] - AAA;
11596         fromY = moveList[currentMove][1] - ONE;
11597
11598         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11599
11600         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11601
11602         if (appData.highlightLastMove) {
11603             SetHighlights(fromX, fromY, toX, toY);
11604         }
11605     }
11606     DisplayMove(currentMove);
11607     SendMoveToProgram(currentMove++, &first);
11608     DisplayBothClocks();
11609     DrawPosition(FALSE, boards[currentMove]);
11610     // [HGM] PV info: always display, routine tests if empty
11611     DisplayComment(currentMove - 1, commentList[currentMove]);
11612     return TRUE;
11613 }
11614
11615
11616 int
11617 LoadGameOneMove (ChessMove readAhead)
11618 {
11619     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11620     char promoChar = NULLCHAR;
11621     ChessMove moveType;
11622     char move[MSG_SIZ];
11623     char *p, *q;
11624
11625     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11626         gameMode != AnalyzeMode && gameMode != Training) {
11627         gameFileFP = NULL;
11628         return FALSE;
11629     }
11630
11631     yyboardindex = forwardMostMove;
11632     if (readAhead != EndOfFile) {
11633       moveType = readAhead;
11634     } else {
11635       if (gameFileFP == NULL)
11636           return FALSE;
11637       moveType = (ChessMove) Myylex();
11638     }
11639
11640     done = FALSE;
11641     switch (moveType) {
11642       case Comment:
11643         if (appData.debugMode)
11644           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11645         p = yy_text;
11646
11647         /* append the comment but don't display it */
11648         AppendComment(currentMove, p, FALSE);
11649         return TRUE;
11650
11651       case WhiteCapturesEnPassant:
11652       case BlackCapturesEnPassant:
11653       case WhitePromotion:
11654       case BlackPromotion:
11655       case WhiteNonPromotion:
11656       case BlackNonPromotion:
11657       case NormalMove:
11658       case FirstLeg:
11659       case WhiteKingSideCastle:
11660       case WhiteQueenSideCastle:
11661       case BlackKingSideCastle:
11662       case BlackQueenSideCastle:
11663       case WhiteKingSideCastleWild:
11664       case WhiteQueenSideCastleWild:
11665       case BlackKingSideCastleWild:
11666       case BlackQueenSideCastleWild:
11667       /* PUSH Fabien */
11668       case WhiteHSideCastleFR:
11669       case WhiteASideCastleFR:
11670       case BlackHSideCastleFR:
11671       case BlackASideCastleFR:
11672       /* POP Fabien */
11673         if (appData.debugMode)
11674           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11675         fromX = currentMoveString[0] - AAA;
11676         fromY = currentMoveString[1] - ONE;
11677         toX = currentMoveString[2] - AAA;
11678         toY = currentMoveString[3] - ONE;
11679         promoChar = currentMoveString[4];
11680         if(promoChar == ';') promoChar = NULLCHAR;
11681         break;
11682
11683       case WhiteDrop:
11684       case BlackDrop:
11685         if (appData.debugMode)
11686           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11687         fromX = moveType == WhiteDrop ?
11688           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11689         (int) CharToPiece(ToLower(currentMoveString[0]));
11690         fromY = DROP_RANK;
11691         toX = currentMoveString[2] - AAA;
11692         toY = currentMoveString[3] - ONE;
11693         break;
11694
11695       case WhiteWins:
11696       case BlackWins:
11697       case GameIsDrawn:
11698       case GameUnfinished:
11699         if (appData.debugMode)
11700           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11701         p = strchr(yy_text, '{');
11702         if (p == NULL) p = strchr(yy_text, '(');
11703         if (p == NULL) {
11704             p = yy_text;
11705             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11706         } else {
11707             q = strchr(p, *p == '{' ? '}' : ')');
11708             if (q != NULL) *q = NULLCHAR;
11709             p++;
11710         }
11711         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11712         GameEnds(moveType, p, GE_FILE);
11713         done = TRUE;
11714         if (cmailMsgLoaded) {
11715             ClearHighlights();
11716             flipView = WhiteOnMove(currentMove);
11717             if (moveType == GameUnfinished) flipView = !flipView;
11718             if (appData.debugMode)
11719               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11720         }
11721         break;
11722
11723       case EndOfFile:
11724         if (appData.debugMode)
11725           fprintf(debugFP, "Parser hit end of file\n");
11726         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11727           case MT_NONE:
11728           case MT_CHECK:
11729             break;
11730           case MT_CHECKMATE:
11731           case MT_STAINMATE:
11732             if (WhiteOnMove(currentMove)) {
11733                 GameEnds(BlackWins, "Black mates", GE_FILE);
11734             } else {
11735                 GameEnds(WhiteWins, "White mates", GE_FILE);
11736             }
11737             break;
11738           case MT_STALEMATE:
11739             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11740             break;
11741         }
11742         done = TRUE;
11743         break;
11744
11745       case MoveNumberOne:
11746         if (lastLoadGameStart == GNUChessGame) {
11747             /* GNUChessGames have numbers, but they aren't move numbers */
11748             if (appData.debugMode)
11749               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11750                       yy_text, (int) moveType);
11751             return LoadGameOneMove(EndOfFile); /* tail recursion */
11752         }
11753         /* else fall thru */
11754
11755       case XBoardGame:
11756       case GNUChessGame:
11757       case PGNTag:
11758         /* Reached start of next game in file */
11759         if (appData.debugMode)
11760           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11761         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11762           case MT_NONE:
11763           case MT_CHECK:
11764             break;
11765           case MT_CHECKMATE:
11766           case MT_STAINMATE:
11767             if (WhiteOnMove(currentMove)) {
11768                 GameEnds(BlackWins, "Black mates", GE_FILE);
11769             } else {
11770                 GameEnds(WhiteWins, "White mates", GE_FILE);
11771             }
11772             break;
11773           case MT_STALEMATE:
11774             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11775             break;
11776         }
11777         done = TRUE;
11778         break;
11779
11780       case PositionDiagram:     /* should not happen; ignore */
11781       case ElapsedTime:         /* ignore */
11782       case NAG:                 /* ignore */
11783         if (appData.debugMode)
11784           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11785                   yy_text, (int) moveType);
11786         return LoadGameOneMove(EndOfFile); /* tail recursion */
11787
11788       case IllegalMove:
11789         if (appData.testLegality) {
11790             if (appData.debugMode)
11791               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11792             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11793                     (forwardMostMove / 2) + 1,
11794                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11795             DisplayError(move, 0);
11796             done = TRUE;
11797         } else {
11798             if (appData.debugMode)
11799               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11800                       yy_text, currentMoveString);
11801             fromX = currentMoveString[0] - AAA;
11802             fromY = currentMoveString[1] - ONE;
11803             toX = currentMoveString[2] - AAA;
11804             toY = currentMoveString[3] - ONE;
11805             promoChar = currentMoveString[4];
11806         }
11807         break;
11808
11809       case AmbiguousMove:
11810         if (appData.debugMode)
11811           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11812         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11813                 (forwardMostMove / 2) + 1,
11814                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11815         DisplayError(move, 0);
11816         done = TRUE;
11817         break;
11818
11819       default:
11820       case ImpossibleMove:
11821         if (appData.debugMode)
11822           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11823         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11824                 (forwardMostMove / 2) + 1,
11825                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11826         DisplayError(move, 0);
11827         done = TRUE;
11828         break;
11829     }
11830
11831     if (done) {
11832         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11833             DrawPosition(FALSE, boards[currentMove]);
11834             DisplayBothClocks();
11835             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11836               DisplayComment(currentMove - 1, commentList[currentMove]);
11837         }
11838         (void) StopLoadGameTimer();
11839         gameFileFP = NULL;
11840         cmailOldMove = forwardMostMove;
11841         return FALSE;
11842     } else {
11843         /* currentMoveString is set as a side-effect of yylex */
11844
11845         thinkOutput[0] = NULLCHAR;
11846         MakeMove(fromX, fromY, toX, toY, promoChar);
11847         killX = killY = -1; // [HGM] lion: used up
11848         currentMove = forwardMostMove;
11849         return TRUE;
11850     }
11851 }
11852
11853 /* Load the nth game from the given file */
11854 int
11855 LoadGameFromFile (char *filename, int n, char *title, int useList)
11856 {
11857     FILE *f;
11858     char buf[MSG_SIZ];
11859
11860     if (strcmp(filename, "-") == 0) {
11861         f = stdin;
11862         title = "stdin";
11863     } else {
11864         f = fopen(filename, "rb");
11865         if (f == NULL) {
11866           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11867             DisplayError(buf, errno);
11868             return FALSE;
11869         }
11870     }
11871     if (fseek(f, 0, 0) == -1) {
11872         /* f is not seekable; probably a pipe */
11873         useList = FALSE;
11874     }
11875     if (useList && n == 0) {
11876         int error = GameListBuild(f);
11877         if (error) {
11878             DisplayError(_("Cannot build game list"), error);
11879         } else if (!ListEmpty(&gameList) &&
11880                    ((ListGame *) gameList.tailPred)->number > 1) {
11881             GameListPopUp(f, title);
11882             return TRUE;
11883         }
11884         GameListDestroy();
11885         n = 1;
11886     }
11887     if (n == 0) n = 1;
11888     return LoadGame(f, n, title, FALSE);
11889 }
11890
11891
11892 void
11893 MakeRegisteredMove ()
11894 {
11895     int fromX, fromY, toX, toY;
11896     char promoChar;
11897     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11898         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11899           case CMAIL_MOVE:
11900           case CMAIL_DRAW:
11901             if (appData.debugMode)
11902               fprintf(debugFP, "Restoring %s for game %d\n",
11903                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11904
11905             thinkOutput[0] = NULLCHAR;
11906             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11907             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11908             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11909             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11910             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11911             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11912             MakeMove(fromX, fromY, toX, toY, promoChar);
11913             ShowMove(fromX, fromY, toX, toY);
11914
11915             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11916               case MT_NONE:
11917               case MT_CHECK:
11918                 break;
11919
11920               case MT_CHECKMATE:
11921               case MT_STAINMATE:
11922                 if (WhiteOnMove(currentMove)) {
11923                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11924                 } else {
11925                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11926                 }
11927                 break;
11928
11929               case MT_STALEMATE:
11930                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11931                 break;
11932             }
11933
11934             break;
11935
11936           case CMAIL_RESIGN:
11937             if (WhiteOnMove(currentMove)) {
11938                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11939             } else {
11940                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11941             }
11942             break;
11943
11944           case CMAIL_ACCEPT:
11945             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11946             break;
11947
11948           default:
11949             break;
11950         }
11951     }
11952
11953     return;
11954 }
11955
11956 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11957 int
11958 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11959 {
11960     int retVal;
11961
11962     if (gameNumber > nCmailGames) {
11963         DisplayError(_("No more games in this message"), 0);
11964         return FALSE;
11965     }
11966     if (f == lastLoadGameFP) {
11967         int offset = gameNumber - lastLoadGameNumber;
11968         if (offset == 0) {
11969             cmailMsg[0] = NULLCHAR;
11970             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11971                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11972                 nCmailMovesRegistered--;
11973             }
11974             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11975             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11976                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11977             }
11978         } else {
11979             if (! RegisterMove()) return FALSE;
11980         }
11981     }
11982
11983     retVal = LoadGame(f, gameNumber, title, useList);
11984
11985     /* Make move registered during previous look at this game, if any */
11986     MakeRegisteredMove();
11987
11988     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11989         commentList[currentMove]
11990           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11991         DisplayComment(currentMove - 1, commentList[currentMove]);
11992     }
11993
11994     return retVal;
11995 }
11996
11997 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11998 int
11999 ReloadGame (int offset)
12000 {
12001     int gameNumber = lastLoadGameNumber + offset;
12002     if (lastLoadGameFP == NULL) {
12003         DisplayError(_("No game has been loaded yet"), 0);
12004         return FALSE;
12005     }
12006     if (gameNumber <= 0) {
12007         DisplayError(_("Can't back up any further"), 0);
12008         return FALSE;
12009     }
12010     if (cmailMsgLoaded) {
12011         return CmailLoadGame(lastLoadGameFP, gameNumber,
12012                              lastLoadGameTitle, lastLoadGameUseList);
12013     } else {
12014         return LoadGame(lastLoadGameFP, gameNumber,
12015                         lastLoadGameTitle, lastLoadGameUseList);
12016     }
12017 }
12018
12019 int keys[EmptySquare+1];
12020
12021 int
12022 PositionMatches (Board b1, Board b2)
12023 {
12024     int r, f, sum=0;
12025     switch(appData.searchMode) {
12026         case 1: return CompareWithRights(b1, b2);
12027         case 2:
12028             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12029                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12030             }
12031             return TRUE;
12032         case 3:
12033             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12034               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12035                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12036             }
12037             return sum==0;
12038         case 4:
12039             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12040                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12041             }
12042             return sum==0;
12043     }
12044     return TRUE;
12045 }
12046
12047 #define Q_PROMO  4
12048 #define Q_EP     3
12049 #define Q_BCASTL 2
12050 #define Q_WCASTL 1
12051
12052 int pieceList[256], quickBoard[256];
12053 ChessSquare pieceType[256] = { EmptySquare };
12054 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12055 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12056 int soughtTotal, turn;
12057 Boolean epOK, flipSearch;
12058
12059 typedef struct {
12060     unsigned char piece, to;
12061 } Move;
12062
12063 #define DSIZE (250000)
12064
12065 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12066 Move *moveDatabase = initialSpace;
12067 unsigned int movePtr, dataSize = DSIZE;
12068
12069 int
12070 MakePieceList (Board board, int *counts)
12071 {
12072     int r, f, n=Q_PROMO, total=0;
12073     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12074     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12075         int sq = f + (r<<4);
12076         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12077             quickBoard[sq] = ++n;
12078             pieceList[n] = sq;
12079             pieceType[n] = board[r][f];
12080             counts[board[r][f]]++;
12081             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12082             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12083             total++;
12084         }
12085     }
12086     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12087     return total;
12088 }
12089
12090 void
12091 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12092 {
12093     int sq = fromX + (fromY<<4);
12094     int piece = quickBoard[sq];
12095     quickBoard[sq] = 0;
12096     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12097     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12098         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12099         moveDatabase[movePtr++].piece = Q_WCASTL;
12100         quickBoard[sq] = piece;
12101         piece = quickBoard[from]; quickBoard[from] = 0;
12102         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12103     } else
12104     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12105         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12106         moveDatabase[movePtr++].piece = Q_BCASTL;
12107         quickBoard[sq] = piece;
12108         piece = quickBoard[from]; quickBoard[from] = 0;
12109         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12110     } else
12111     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12112         quickBoard[(fromY<<4)+toX] = 0;
12113         moveDatabase[movePtr].piece = Q_EP;
12114         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12115         moveDatabase[movePtr].to = sq;
12116     } else
12117     if(promoPiece != pieceType[piece]) {
12118         moveDatabase[movePtr++].piece = Q_PROMO;
12119         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12120     }
12121     moveDatabase[movePtr].piece = piece;
12122     quickBoard[sq] = piece;
12123     movePtr++;
12124 }
12125
12126 int
12127 PackGame (Board board)
12128 {
12129     Move *newSpace = NULL;
12130     moveDatabase[movePtr].piece = 0; // terminate previous game
12131     if(movePtr > dataSize) {
12132         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12133         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12134         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12135         if(newSpace) {
12136             int i;
12137             Move *p = moveDatabase, *q = newSpace;
12138             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12139             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12140             moveDatabase = newSpace;
12141         } else { // calloc failed, we must be out of memory. Too bad...
12142             dataSize = 0; // prevent calloc events for all subsequent games
12143             return 0;     // and signal this one isn't cached
12144         }
12145     }
12146     movePtr++;
12147     MakePieceList(board, counts);
12148     return movePtr;
12149 }
12150
12151 int
12152 QuickCompare (Board board, int *minCounts, int *maxCounts)
12153 {   // compare according to search mode
12154     int r, f;
12155     switch(appData.searchMode)
12156     {
12157       case 1: // exact position match
12158         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12159         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12160             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12161         }
12162         break;
12163       case 2: // can have extra material on empty squares
12164         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12165             if(board[r][f] == EmptySquare) continue;
12166             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12167         }
12168         break;
12169       case 3: // material with exact Pawn structure
12170         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12171             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12172             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12173         } // fall through to material comparison
12174       case 4: // exact material
12175         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12176         break;
12177       case 6: // material range with given imbalance
12178         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12179         // fall through to range comparison
12180       case 5: // material range
12181         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12182     }
12183     return TRUE;
12184 }
12185
12186 int
12187 QuickScan (Board board, Move *move)
12188 {   // reconstruct game,and compare all positions in it
12189     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12190     do {
12191         int piece = move->piece;
12192         int to = move->to, from = pieceList[piece];
12193         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12194           if(!piece) return -1;
12195           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12196             piece = (++move)->piece;
12197             from = pieceList[piece];
12198             counts[pieceType[piece]]--;
12199             pieceType[piece] = (ChessSquare) move->to;
12200             counts[move->to]++;
12201           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12202             counts[pieceType[quickBoard[to]]]--;
12203             quickBoard[to] = 0; total--;
12204             move++;
12205             continue;
12206           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12207             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12208             from  = pieceList[piece]; // so this must be King
12209             quickBoard[from] = 0;
12210             pieceList[piece] = to;
12211             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12212             quickBoard[from] = 0; // rook
12213             quickBoard[to] = piece;
12214             to = move->to; piece = move->piece;
12215             goto aftercastle;
12216           }
12217         }
12218         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12219         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12220         quickBoard[from] = 0;
12221       aftercastle:
12222         quickBoard[to] = piece;
12223         pieceList[piece] = to;
12224         cnt++; turn ^= 3;
12225         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12226            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12227            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12228                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12229           ) {
12230             static int lastCounts[EmptySquare+1];
12231             int i;
12232             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12233             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12234         } else stretch = 0;
12235         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12236         move++;
12237     } while(1);
12238 }
12239
12240 void
12241 InitSearch ()
12242 {
12243     int r, f;
12244     flipSearch = FALSE;
12245     CopyBoard(soughtBoard, boards[currentMove]);
12246     soughtTotal = MakePieceList(soughtBoard, maxSought);
12247     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12248     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12249     CopyBoard(reverseBoard, boards[currentMove]);
12250     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12251         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12252         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12253         reverseBoard[r][f] = piece;
12254     }
12255     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12256     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12257     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12258                  || (boards[currentMove][CASTLING][2] == NoRights ||
12259                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12260                  && (boards[currentMove][CASTLING][5] == NoRights ||
12261                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12262       ) {
12263         flipSearch = TRUE;
12264         CopyBoard(flipBoard, soughtBoard);
12265         CopyBoard(rotateBoard, reverseBoard);
12266         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12267             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12268             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12269         }
12270     }
12271     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12272     if(appData.searchMode >= 5) {
12273         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12274         MakePieceList(soughtBoard, minSought);
12275         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12276     }
12277     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12278         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12279 }
12280
12281 GameInfo dummyInfo;
12282 static int creatingBook;
12283
12284 int
12285 GameContainsPosition (FILE *f, ListGame *lg)
12286 {
12287     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12288     int fromX, fromY, toX, toY;
12289     char promoChar;
12290     static int initDone=FALSE;
12291
12292     // weed out games based on numerical tag comparison
12293     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12294     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12295     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12296     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12297     if(!initDone) {
12298         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12299         initDone = TRUE;
12300     }
12301     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12302     else CopyBoard(boards[scratch], initialPosition); // default start position
12303     if(lg->moves) {
12304         turn = btm + 1;
12305         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12306         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12307     }
12308     if(btm) plyNr++;
12309     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12310     fseek(f, lg->offset, 0);
12311     yynewfile(f);
12312     while(1) {
12313         yyboardindex = scratch;
12314         quickFlag = plyNr+1;
12315         next = Myylex();
12316         quickFlag = 0;
12317         switch(next) {
12318             case PGNTag:
12319                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12320             default:
12321                 continue;
12322
12323             case XBoardGame:
12324             case GNUChessGame:
12325                 if(plyNr) return -1; // after we have seen moves, this is for new game
12326               continue;
12327
12328             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12329             case ImpossibleMove:
12330             case WhiteWins: // game ends here with these four
12331             case BlackWins:
12332             case GameIsDrawn:
12333             case GameUnfinished:
12334                 return -1;
12335
12336             case IllegalMove:
12337                 if(appData.testLegality) return -1;
12338             case WhiteCapturesEnPassant:
12339             case BlackCapturesEnPassant:
12340             case WhitePromotion:
12341             case BlackPromotion:
12342             case WhiteNonPromotion:
12343             case BlackNonPromotion:
12344             case NormalMove:
12345             case FirstLeg:
12346             case WhiteKingSideCastle:
12347             case WhiteQueenSideCastle:
12348             case BlackKingSideCastle:
12349             case BlackQueenSideCastle:
12350             case WhiteKingSideCastleWild:
12351             case WhiteQueenSideCastleWild:
12352             case BlackKingSideCastleWild:
12353             case BlackQueenSideCastleWild:
12354             case WhiteHSideCastleFR:
12355             case WhiteASideCastleFR:
12356             case BlackHSideCastleFR:
12357             case BlackASideCastleFR:
12358                 fromX = currentMoveString[0] - AAA;
12359                 fromY = currentMoveString[1] - ONE;
12360                 toX = currentMoveString[2] - AAA;
12361                 toY = currentMoveString[3] - ONE;
12362                 promoChar = currentMoveString[4];
12363                 break;
12364             case WhiteDrop:
12365             case BlackDrop:
12366                 fromX = next == WhiteDrop ?
12367                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12368                   (int) CharToPiece(ToLower(currentMoveString[0]));
12369                 fromY = DROP_RANK;
12370                 toX = currentMoveString[2] - AAA;
12371                 toY = currentMoveString[3] - ONE;
12372                 promoChar = 0;
12373                 break;
12374         }
12375         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12376         plyNr++;
12377         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12378         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12379         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12380         if(appData.findMirror) {
12381             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12382             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12383         }
12384     }
12385 }
12386
12387 /* Load the nth game from open file f */
12388 int
12389 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12390 {
12391     ChessMove cm;
12392     char buf[MSG_SIZ];
12393     int gn = gameNumber;
12394     ListGame *lg = NULL;
12395     int numPGNTags = 0;
12396     int err, pos = -1;
12397     GameMode oldGameMode;
12398     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12399
12400     if (appData.debugMode)
12401         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12402
12403     if (gameMode == Training )
12404         SetTrainingModeOff();
12405
12406     oldGameMode = gameMode;
12407     if (gameMode != BeginningOfGame) {
12408       Reset(FALSE, TRUE);
12409     }
12410     killX = killY = -1; // [HGM] lion: in case we did not Reset
12411
12412     gameFileFP = f;
12413     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12414         fclose(lastLoadGameFP);
12415     }
12416
12417     if (useList) {
12418         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12419
12420         if (lg) {
12421             fseek(f, lg->offset, 0);
12422             GameListHighlight(gameNumber);
12423             pos = lg->position;
12424             gn = 1;
12425         }
12426         else {
12427             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12428               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12429             else
12430             DisplayError(_("Game number out of range"), 0);
12431             return FALSE;
12432         }
12433     } else {
12434         GameListDestroy();
12435         if (fseek(f, 0, 0) == -1) {
12436             if (f == lastLoadGameFP ?
12437                 gameNumber == lastLoadGameNumber + 1 :
12438                 gameNumber == 1) {
12439                 gn = 1;
12440             } else {
12441                 DisplayError(_("Can't seek on game file"), 0);
12442                 return FALSE;
12443             }
12444         }
12445     }
12446     lastLoadGameFP = f;
12447     lastLoadGameNumber = gameNumber;
12448     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12449     lastLoadGameUseList = useList;
12450
12451     yynewfile(f);
12452
12453     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12454       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12455                 lg->gameInfo.black);
12456             DisplayTitle(buf);
12457     } else if (*title != NULLCHAR) {
12458         if (gameNumber > 1) {
12459           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12460             DisplayTitle(buf);
12461         } else {
12462             DisplayTitle(title);
12463         }
12464     }
12465
12466     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12467         gameMode = PlayFromGameFile;
12468         ModeHighlight();
12469     }
12470
12471     currentMove = forwardMostMove = backwardMostMove = 0;
12472     CopyBoard(boards[0], initialPosition);
12473     StopClocks();
12474
12475     /*
12476      * Skip the first gn-1 games in the file.
12477      * Also skip over anything that precedes an identifiable
12478      * start of game marker, to avoid being confused by
12479      * garbage at the start of the file.  Currently
12480      * recognized start of game markers are the move number "1",
12481      * the pattern "gnuchess .* game", the pattern
12482      * "^[#;%] [^ ]* game file", and a PGN tag block.
12483      * A game that starts with one of the latter two patterns
12484      * will also have a move number 1, possibly
12485      * following a position diagram.
12486      * 5-4-02: Let's try being more lenient and allowing a game to
12487      * start with an unnumbered move.  Does that break anything?
12488      */
12489     cm = lastLoadGameStart = EndOfFile;
12490     while (gn > 0) {
12491         yyboardindex = forwardMostMove;
12492         cm = (ChessMove) Myylex();
12493         switch (cm) {
12494           case EndOfFile:
12495             if (cmailMsgLoaded) {
12496                 nCmailGames = CMAIL_MAX_GAMES - gn;
12497             } else {
12498                 Reset(TRUE, TRUE);
12499                 DisplayError(_("Game not found in file"), 0);
12500             }
12501             return FALSE;
12502
12503           case GNUChessGame:
12504           case XBoardGame:
12505             gn--;
12506             lastLoadGameStart = cm;
12507             break;
12508
12509           case MoveNumberOne:
12510             switch (lastLoadGameStart) {
12511               case GNUChessGame:
12512               case XBoardGame:
12513               case PGNTag:
12514                 break;
12515               case MoveNumberOne:
12516               case EndOfFile:
12517                 gn--;           /* count this game */
12518                 lastLoadGameStart = cm;
12519                 break;
12520               default:
12521                 /* impossible */
12522                 break;
12523             }
12524             break;
12525
12526           case PGNTag:
12527             switch (lastLoadGameStart) {
12528               case GNUChessGame:
12529               case PGNTag:
12530               case MoveNumberOne:
12531               case EndOfFile:
12532                 gn--;           /* count this game */
12533                 lastLoadGameStart = cm;
12534                 break;
12535               case XBoardGame:
12536                 lastLoadGameStart = cm; /* game counted already */
12537                 break;
12538               default:
12539                 /* impossible */
12540                 break;
12541             }
12542             if (gn > 0) {
12543                 do {
12544                     yyboardindex = forwardMostMove;
12545                     cm = (ChessMove) Myylex();
12546                 } while (cm == PGNTag || cm == Comment);
12547             }
12548             break;
12549
12550           case WhiteWins:
12551           case BlackWins:
12552           case GameIsDrawn:
12553             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12554                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12555                     != CMAIL_OLD_RESULT) {
12556                     nCmailResults ++ ;
12557                     cmailResult[  CMAIL_MAX_GAMES
12558                                 - gn - 1] = CMAIL_OLD_RESULT;
12559                 }
12560             }
12561             break;
12562
12563           case NormalMove:
12564           case FirstLeg:
12565             /* Only a NormalMove can be at the start of a game
12566              * without a position diagram. */
12567             if (lastLoadGameStart == EndOfFile ) {
12568               gn--;
12569               lastLoadGameStart = MoveNumberOne;
12570             }
12571             break;
12572
12573           default:
12574             break;
12575         }
12576     }
12577
12578     if (appData.debugMode)
12579       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12580
12581     if (cm == XBoardGame) {
12582         /* Skip any header junk before position diagram and/or move 1 */
12583         for (;;) {
12584             yyboardindex = forwardMostMove;
12585             cm = (ChessMove) Myylex();
12586
12587             if (cm == EndOfFile ||
12588                 cm == GNUChessGame || cm == XBoardGame) {
12589                 /* Empty game; pretend end-of-file and handle later */
12590                 cm = EndOfFile;
12591                 break;
12592             }
12593
12594             if (cm == MoveNumberOne || cm == PositionDiagram ||
12595                 cm == PGNTag || cm == Comment)
12596               break;
12597         }
12598     } else if (cm == GNUChessGame) {
12599         if (gameInfo.event != NULL) {
12600             free(gameInfo.event);
12601         }
12602         gameInfo.event = StrSave(yy_text);
12603     }
12604
12605     startedFromSetupPosition = FALSE;
12606     while (cm == PGNTag) {
12607         if (appData.debugMode)
12608           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12609         err = ParsePGNTag(yy_text, &gameInfo);
12610         if (!err) numPGNTags++;
12611
12612         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12613         if(gameInfo.variant != oldVariant) {
12614             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12615             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12616             InitPosition(TRUE);
12617             oldVariant = gameInfo.variant;
12618             if (appData.debugMode)
12619               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12620         }
12621
12622
12623         if (gameInfo.fen != NULL) {
12624           Board initial_position;
12625           startedFromSetupPosition = TRUE;
12626           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12627             Reset(TRUE, TRUE);
12628             DisplayError(_("Bad FEN position in file"), 0);
12629             return FALSE;
12630           }
12631           CopyBoard(boards[0], initial_position);
12632           if (blackPlaysFirst) {
12633             currentMove = forwardMostMove = backwardMostMove = 1;
12634             CopyBoard(boards[1], initial_position);
12635             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12636             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12637             timeRemaining[0][1] = whiteTimeRemaining;
12638             timeRemaining[1][1] = blackTimeRemaining;
12639             if (commentList[0] != NULL) {
12640               commentList[1] = commentList[0];
12641               commentList[0] = NULL;
12642             }
12643           } else {
12644             currentMove = forwardMostMove = backwardMostMove = 0;
12645           }
12646           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12647           {   int i;
12648               initialRulePlies = FENrulePlies;
12649               for( i=0; i< nrCastlingRights; i++ )
12650                   initialRights[i] = initial_position[CASTLING][i];
12651           }
12652           yyboardindex = forwardMostMove;
12653           free(gameInfo.fen);
12654           gameInfo.fen = NULL;
12655         }
12656
12657         yyboardindex = forwardMostMove;
12658         cm = (ChessMove) Myylex();
12659
12660         /* Handle comments interspersed among the tags */
12661         while (cm == Comment) {
12662             char *p;
12663             if (appData.debugMode)
12664               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12665             p = yy_text;
12666             AppendComment(currentMove, p, FALSE);
12667             yyboardindex = forwardMostMove;
12668             cm = (ChessMove) Myylex();
12669         }
12670     }
12671
12672     /* don't rely on existence of Event tag since if game was
12673      * pasted from clipboard the Event tag may not exist
12674      */
12675     if (numPGNTags > 0){
12676         char *tags;
12677         if (gameInfo.variant == VariantNormal) {
12678           VariantClass v = StringToVariant(gameInfo.event);
12679           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12680           if(v < VariantShogi) gameInfo.variant = v;
12681         }
12682         if (!matchMode) {
12683           if( appData.autoDisplayTags ) {
12684             tags = PGNTags(&gameInfo);
12685             TagsPopUp(tags, CmailMsg());
12686             free(tags);
12687           }
12688         }
12689     } else {
12690         /* Make something up, but don't display it now */
12691         SetGameInfo();
12692         TagsPopDown();
12693     }
12694
12695     if (cm == PositionDiagram) {
12696         int i, j;
12697         char *p;
12698         Board initial_position;
12699
12700         if (appData.debugMode)
12701           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12702
12703         if (!startedFromSetupPosition) {
12704             p = yy_text;
12705             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12706               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12707                 switch (*p) {
12708                   case '{':
12709                   case '[':
12710                   case '-':
12711                   case ' ':
12712                   case '\t':
12713                   case '\n':
12714                   case '\r':
12715                     break;
12716                   default:
12717                     initial_position[i][j++] = CharToPiece(*p);
12718                     break;
12719                 }
12720             while (*p == ' ' || *p == '\t' ||
12721                    *p == '\n' || *p == '\r') p++;
12722
12723             if (strncmp(p, "black", strlen("black"))==0)
12724               blackPlaysFirst = TRUE;
12725             else
12726               blackPlaysFirst = FALSE;
12727             startedFromSetupPosition = TRUE;
12728
12729             CopyBoard(boards[0], initial_position);
12730             if (blackPlaysFirst) {
12731                 currentMove = forwardMostMove = backwardMostMove = 1;
12732                 CopyBoard(boards[1], initial_position);
12733                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12734                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12735                 timeRemaining[0][1] = whiteTimeRemaining;
12736                 timeRemaining[1][1] = blackTimeRemaining;
12737                 if (commentList[0] != NULL) {
12738                     commentList[1] = commentList[0];
12739                     commentList[0] = NULL;
12740                 }
12741             } else {
12742                 currentMove = forwardMostMove = backwardMostMove = 0;
12743             }
12744         }
12745         yyboardindex = forwardMostMove;
12746         cm = (ChessMove) Myylex();
12747     }
12748
12749   if(!creatingBook) {
12750     if (first.pr == NoProc) {
12751         StartChessProgram(&first);
12752     }
12753     InitChessProgram(&first, FALSE);
12754     SendToProgram("force\n", &first);
12755     if (startedFromSetupPosition) {
12756         SendBoard(&first, forwardMostMove);
12757     if (appData.debugMode) {
12758         fprintf(debugFP, "Load Game\n");
12759     }
12760         DisplayBothClocks();
12761     }
12762   }
12763
12764     /* [HGM] server: flag to write setup moves in broadcast file as one */
12765     loadFlag = appData.suppressLoadMoves;
12766
12767     while (cm == Comment) {
12768         char *p;
12769         if (appData.debugMode)
12770           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12771         p = yy_text;
12772         AppendComment(currentMove, p, FALSE);
12773         yyboardindex = forwardMostMove;
12774         cm = (ChessMove) Myylex();
12775     }
12776
12777     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12778         cm == WhiteWins || cm == BlackWins ||
12779         cm == GameIsDrawn || cm == GameUnfinished) {
12780         DisplayMessage("", _("No moves in game"));
12781         if (cmailMsgLoaded) {
12782             if (appData.debugMode)
12783               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12784             ClearHighlights();
12785             flipView = FALSE;
12786         }
12787         DrawPosition(FALSE, boards[currentMove]);
12788         DisplayBothClocks();
12789         gameMode = EditGame;
12790         ModeHighlight();
12791         gameFileFP = NULL;
12792         cmailOldMove = 0;
12793         return TRUE;
12794     }
12795
12796     // [HGM] PV info: routine tests if comment empty
12797     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12798         DisplayComment(currentMove - 1, commentList[currentMove]);
12799     }
12800     if (!matchMode && appData.timeDelay != 0)
12801       DrawPosition(FALSE, boards[currentMove]);
12802
12803     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12804       programStats.ok_to_send = 1;
12805     }
12806
12807     /* if the first token after the PGN tags is a move
12808      * and not move number 1, retrieve it from the parser
12809      */
12810     if (cm != MoveNumberOne)
12811         LoadGameOneMove(cm);
12812
12813     /* load the remaining moves from the file */
12814     while (LoadGameOneMove(EndOfFile)) {
12815       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12816       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12817     }
12818
12819     /* rewind to the start of the game */
12820     currentMove = backwardMostMove;
12821
12822     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12823
12824     if (oldGameMode == AnalyzeFile) {
12825       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12826       AnalyzeFileEvent();
12827     } else
12828     if (oldGameMode == AnalyzeMode) {
12829       AnalyzeFileEvent();
12830     }
12831
12832     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12833         long int w, b; // [HGM] adjourn: restore saved clock times
12834         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12835         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12836             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12837             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12838         }
12839     }
12840
12841     if(creatingBook) return TRUE;
12842     if (!matchMode && pos > 0) {
12843         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12844     } else
12845     if (matchMode || appData.timeDelay == 0) {
12846       ToEndEvent();
12847     } else if (appData.timeDelay > 0) {
12848       AutoPlayGameLoop();
12849     }
12850
12851     if (appData.debugMode)
12852         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12853
12854     loadFlag = 0; /* [HGM] true game starts */
12855     return TRUE;
12856 }
12857
12858 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12859 int
12860 ReloadPosition (int offset)
12861 {
12862     int positionNumber = lastLoadPositionNumber + offset;
12863     if (lastLoadPositionFP == NULL) {
12864         DisplayError(_("No position has been loaded yet"), 0);
12865         return FALSE;
12866     }
12867     if (positionNumber <= 0) {
12868         DisplayError(_("Can't back up any further"), 0);
12869         return FALSE;
12870     }
12871     return LoadPosition(lastLoadPositionFP, positionNumber,
12872                         lastLoadPositionTitle);
12873 }
12874
12875 /* Load the nth position from the given file */
12876 int
12877 LoadPositionFromFile (char *filename, int n, char *title)
12878 {
12879     FILE *f;
12880     char buf[MSG_SIZ];
12881
12882     if (strcmp(filename, "-") == 0) {
12883         return LoadPosition(stdin, n, "stdin");
12884     } else {
12885         f = fopen(filename, "rb");
12886         if (f == NULL) {
12887             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12888             DisplayError(buf, errno);
12889             return FALSE;
12890         } else {
12891             return LoadPosition(f, n, title);
12892         }
12893     }
12894 }
12895
12896 /* Load the nth position from the given open file, and close it */
12897 int
12898 LoadPosition (FILE *f, int positionNumber, char *title)
12899 {
12900     char *p, line[MSG_SIZ];
12901     Board initial_position;
12902     int i, j, fenMode, pn;
12903
12904     if (gameMode == Training )
12905         SetTrainingModeOff();
12906
12907     if (gameMode != BeginningOfGame) {
12908         Reset(FALSE, TRUE);
12909     }
12910     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12911         fclose(lastLoadPositionFP);
12912     }
12913     if (positionNumber == 0) positionNumber = 1;
12914     lastLoadPositionFP = f;
12915     lastLoadPositionNumber = positionNumber;
12916     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12917     if (first.pr == NoProc && !appData.noChessProgram) {
12918       StartChessProgram(&first);
12919       InitChessProgram(&first, FALSE);
12920     }
12921     pn = positionNumber;
12922     if (positionNumber < 0) {
12923         /* Negative position number means to seek to that byte offset */
12924         if (fseek(f, -positionNumber, 0) == -1) {
12925             DisplayError(_("Can't seek on position file"), 0);
12926             return FALSE;
12927         };
12928         pn = 1;
12929     } else {
12930         if (fseek(f, 0, 0) == -1) {
12931             if (f == lastLoadPositionFP ?
12932                 positionNumber == lastLoadPositionNumber + 1 :
12933                 positionNumber == 1) {
12934                 pn = 1;
12935             } else {
12936                 DisplayError(_("Can't seek on position file"), 0);
12937                 return FALSE;
12938             }
12939         }
12940     }
12941     /* See if this file is FEN or old-style xboard */
12942     if (fgets(line, MSG_SIZ, f) == NULL) {
12943         DisplayError(_("Position not found in file"), 0);
12944         return FALSE;
12945     }
12946     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12947     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12948
12949     if (pn >= 2) {
12950         if (fenMode || line[0] == '#') pn--;
12951         while (pn > 0) {
12952             /* skip positions before number pn */
12953             if (fgets(line, MSG_SIZ, f) == NULL) {
12954                 Reset(TRUE, TRUE);
12955                 DisplayError(_("Position not found in file"), 0);
12956                 return FALSE;
12957             }
12958             if (fenMode || line[0] == '#') pn--;
12959         }
12960     }
12961
12962     if (fenMode) {
12963         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12964             DisplayError(_("Bad FEN position in file"), 0);
12965             return FALSE;
12966         }
12967     } else {
12968         (void) fgets(line, MSG_SIZ, f);
12969         (void) fgets(line, MSG_SIZ, f);
12970
12971         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12972             (void) fgets(line, MSG_SIZ, f);
12973             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12974                 if (*p == ' ')
12975                   continue;
12976                 initial_position[i][j++] = CharToPiece(*p);
12977             }
12978         }
12979
12980         blackPlaysFirst = FALSE;
12981         if (!feof(f)) {
12982             (void) fgets(line, MSG_SIZ, f);
12983             if (strncmp(line, "black", strlen("black"))==0)
12984               blackPlaysFirst = TRUE;
12985         }
12986     }
12987     startedFromSetupPosition = TRUE;
12988
12989     CopyBoard(boards[0], initial_position);
12990     if (blackPlaysFirst) {
12991         currentMove = forwardMostMove = backwardMostMove = 1;
12992         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12993         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12994         CopyBoard(boards[1], initial_position);
12995         DisplayMessage("", _("Black to play"));
12996     } else {
12997         currentMove = forwardMostMove = backwardMostMove = 0;
12998         DisplayMessage("", _("White to play"));
12999     }
13000     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13001     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13002         SendToProgram("force\n", &first);
13003         SendBoard(&first, forwardMostMove);
13004     }
13005     if (appData.debugMode) {
13006 int i, j;
13007   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13008   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13009         fprintf(debugFP, "Load Position\n");
13010     }
13011
13012     if (positionNumber > 1) {
13013       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13014         DisplayTitle(line);
13015     } else {
13016         DisplayTitle(title);
13017     }
13018     gameMode = EditGame;
13019     ModeHighlight();
13020     ResetClocks();
13021     timeRemaining[0][1] = whiteTimeRemaining;
13022     timeRemaining[1][1] = blackTimeRemaining;
13023     DrawPosition(FALSE, boards[currentMove]);
13024
13025     return TRUE;
13026 }
13027
13028
13029 void
13030 CopyPlayerNameIntoFileName (char **dest, char *src)
13031 {
13032     while (*src != NULLCHAR && *src != ',') {
13033         if (*src == ' ') {
13034             *(*dest)++ = '_';
13035             src++;
13036         } else {
13037             *(*dest)++ = *src++;
13038         }
13039     }
13040 }
13041
13042 char *
13043 DefaultFileName (char *ext)
13044 {
13045     static char def[MSG_SIZ];
13046     char *p;
13047
13048     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13049         p = def;
13050         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13051         *p++ = '-';
13052         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13053         *p++ = '.';
13054         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13055     } else {
13056         def[0] = NULLCHAR;
13057     }
13058     return def;
13059 }
13060
13061 /* Save the current game to the given file */
13062 int
13063 SaveGameToFile (char *filename, int append)
13064 {
13065     FILE *f;
13066     char buf[MSG_SIZ];
13067     int result, i, t,tot=0;
13068
13069     if (strcmp(filename, "-") == 0) {
13070         return SaveGame(stdout, 0, NULL);
13071     } else {
13072         for(i=0; i<10; i++) { // upto 10 tries
13073              f = fopen(filename, append ? "a" : "w");
13074              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13075              if(f || errno != 13) break;
13076              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13077              tot += t;
13078         }
13079         if (f == NULL) {
13080             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13081             DisplayError(buf, errno);
13082             return FALSE;
13083         } else {
13084             safeStrCpy(buf, lastMsg, MSG_SIZ);
13085             DisplayMessage(_("Waiting for access to save file"), "");
13086             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13087             DisplayMessage(_("Saving game"), "");
13088             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13089             result = SaveGame(f, 0, NULL);
13090             DisplayMessage(buf, "");
13091             return result;
13092         }
13093     }
13094 }
13095
13096 char *
13097 SavePart (char *str)
13098 {
13099     static char buf[MSG_SIZ];
13100     char *p;
13101
13102     p = strchr(str, ' ');
13103     if (p == NULL) return str;
13104     strncpy(buf, str, p - str);
13105     buf[p - str] = NULLCHAR;
13106     return buf;
13107 }
13108
13109 #define PGN_MAX_LINE 75
13110
13111 #define PGN_SIDE_WHITE  0
13112 #define PGN_SIDE_BLACK  1
13113
13114 static int
13115 FindFirstMoveOutOfBook (int side)
13116 {
13117     int result = -1;
13118
13119     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13120         int index = backwardMostMove;
13121         int has_book_hit = 0;
13122
13123         if( (index % 2) != side ) {
13124             index++;
13125         }
13126
13127         while( index < forwardMostMove ) {
13128             /* Check to see if engine is in book */
13129             int depth = pvInfoList[index].depth;
13130             int score = pvInfoList[index].score;
13131             int in_book = 0;
13132
13133             if( depth <= 2 ) {
13134                 in_book = 1;
13135             }
13136             else if( score == 0 && depth == 63 ) {
13137                 in_book = 1; /* Zappa */
13138             }
13139             else if( score == 2 && depth == 99 ) {
13140                 in_book = 1; /* Abrok */
13141             }
13142
13143             has_book_hit += in_book;
13144
13145             if( ! in_book ) {
13146                 result = index;
13147
13148                 break;
13149             }
13150
13151             index += 2;
13152         }
13153     }
13154
13155     return result;
13156 }
13157
13158 void
13159 GetOutOfBookInfo (char * buf)
13160 {
13161     int oob[2];
13162     int i;
13163     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13164
13165     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13166     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13167
13168     *buf = '\0';
13169
13170     if( oob[0] >= 0 || oob[1] >= 0 ) {
13171         for( i=0; i<2; i++ ) {
13172             int idx = oob[i];
13173
13174             if( idx >= 0 ) {
13175                 if( i > 0 && oob[0] >= 0 ) {
13176                     strcat( buf, "   " );
13177                 }
13178
13179                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13180                 sprintf( buf+strlen(buf), "%s%.2f",
13181                     pvInfoList[idx].score >= 0 ? "+" : "",
13182                     pvInfoList[idx].score / 100.0 );
13183             }
13184         }
13185     }
13186 }
13187
13188 /* Save game in PGN style and close the file */
13189 int
13190 SaveGamePGN (FILE *f)
13191 {
13192     int i, offset, linelen, newblock;
13193 //    char *movetext;
13194     char numtext[32];
13195     int movelen, numlen, blank;
13196     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13197
13198     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13199
13200     PrintPGNTags(f, &gameInfo);
13201
13202     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13203
13204     if (backwardMostMove > 0 || startedFromSetupPosition) {
13205         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13206         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13207         fprintf(f, "\n{--------------\n");
13208         PrintPosition(f, backwardMostMove);
13209         fprintf(f, "--------------}\n");
13210         free(fen);
13211     }
13212     else {
13213         /* [AS] Out of book annotation */
13214         if( appData.saveOutOfBookInfo ) {
13215             char buf[64];
13216
13217             GetOutOfBookInfo( buf );
13218
13219             if( buf[0] != '\0' ) {
13220                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13221             }
13222         }
13223
13224         fprintf(f, "\n");
13225     }
13226
13227     i = backwardMostMove;
13228     linelen = 0;
13229     newblock = TRUE;
13230
13231     while (i < forwardMostMove) {
13232         /* Print comments preceding this move */
13233         if (commentList[i] != NULL) {
13234             if (linelen > 0) fprintf(f, "\n");
13235             fprintf(f, "%s", commentList[i]);
13236             linelen = 0;
13237             newblock = TRUE;
13238         }
13239
13240         /* Format move number */
13241         if ((i % 2) == 0)
13242           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13243         else
13244           if (newblock)
13245             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13246           else
13247             numtext[0] = NULLCHAR;
13248
13249         numlen = strlen(numtext);
13250         newblock = FALSE;
13251
13252         /* Print move number */
13253         blank = linelen > 0 && numlen > 0;
13254         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13255             fprintf(f, "\n");
13256             linelen = 0;
13257             blank = 0;
13258         }
13259         if (blank) {
13260             fprintf(f, " ");
13261             linelen++;
13262         }
13263         fprintf(f, "%s", numtext);
13264         linelen += numlen;
13265
13266         /* Get move */
13267         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13268         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13269
13270         /* Print move */
13271         blank = linelen > 0 && movelen > 0;
13272         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13273             fprintf(f, "\n");
13274             linelen = 0;
13275             blank = 0;
13276         }
13277         if (blank) {
13278             fprintf(f, " ");
13279             linelen++;
13280         }
13281         fprintf(f, "%s", move_buffer);
13282         linelen += movelen;
13283
13284         /* [AS] Add PV info if present */
13285         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13286             /* [HGM] add time */
13287             char buf[MSG_SIZ]; int seconds;
13288
13289             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13290
13291             if( seconds <= 0)
13292               buf[0] = 0;
13293             else
13294               if( seconds < 30 )
13295                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13296               else
13297                 {
13298                   seconds = (seconds + 4)/10; // round to full seconds
13299                   if( seconds < 60 )
13300                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13301                   else
13302                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13303                 }
13304
13305             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13306                       pvInfoList[i].score >= 0 ? "+" : "",
13307                       pvInfoList[i].score / 100.0,
13308                       pvInfoList[i].depth,
13309                       buf );
13310
13311             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13312
13313             /* Print score/depth */
13314             blank = linelen > 0 && movelen > 0;
13315             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13316                 fprintf(f, "\n");
13317                 linelen = 0;
13318                 blank = 0;
13319             }
13320             if (blank) {
13321                 fprintf(f, " ");
13322                 linelen++;
13323             }
13324             fprintf(f, "%s", move_buffer);
13325             linelen += movelen;
13326         }
13327
13328         i++;
13329     }
13330
13331     /* Start a new line */
13332     if (linelen > 0) fprintf(f, "\n");
13333
13334     /* Print comments after last move */
13335     if (commentList[i] != NULL) {
13336         fprintf(f, "%s\n", commentList[i]);
13337     }
13338
13339     /* Print result */
13340     if (gameInfo.resultDetails != NULL &&
13341         gameInfo.resultDetails[0] != NULLCHAR) {
13342         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13343         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13344            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13345             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13346         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13347     } else {
13348         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13349     }
13350
13351     fclose(f);
13352     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13353     return TRUE;
13354 }
13355
13356 /* Save game in old style and close the file */
13357 int
13358 SaveGameOldStyle (FILE *f)
13359 {
13360     int i, offset;
13361     time_t tm;
13362
13363     tm = time((time_t *) NULL);
13364
13365     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13366     PrintOpponents(f);
13367
13368     if (backwardMostMove > 0 || startedFromSetupPosition) {
13369         fprintf(f, "\n[--------------\n");
13370         PrintPosition(f, backwardMostMove);
13371         fprintf(f, "--------------]\n");
13372     } else {
13373         fprintf(f, "\n");
13374     }
13375
13376     i = backwardMostMove;
13377     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13378
13379     while (i < forwardMostMove) {
13380         if (commentList[i] != NULL) {
13381             fprintf(f, "[%s]\n", commentList[i]);
13382         }
13383
13384         if ((i % 2) == 1) {
13385             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13386             i++;
13387         } else {
13388             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13389             i++;
13390             if (commentList[i] != NULL) {
13391                 fprintf(f, "\n");
13392                 continue;
13393             }
13394             if (i >= forwardMostMove) {
13395                 fprintf(f, "\n");
13396                 break;
13397             }
13398             fprintf(f, "%s\n", parseList[i]);
13399             i++;
13400         }
13401     }
13402
13403     if (commentList[i] != NULL) {
13404         fprintf(f, "[%s]\n", commentList[i]);
13405     }
13406
13407     /* This isn't really the old style, but it's close enough */
13408     if (gameInfo.resultDetails != NULL &&
13409         gameInfo.resultDetails[0] != NULLCHAR) {
13410         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13411                 gameInfo.resultDetails);
13412     } else {
13413         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13414     }
13415
13416     fclose(f);
13417     return TRUE;
13418 }
13419
13420 /* Save the current game to open file f and close the file */
13421 int
13422 SaveGame (FILE *f, int dummy, char *dummy2)
13423 {
13424     if (gameMode == EditPosition) EditPositionDone(TRUE);
13425     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13426     if (appData.oldSaveStyle)
13427       return SaveGameOldStyle(f);
13428     else
13429       return SaveGamePGN(f);
13430 }
13431
13432 /* Save the current position to the given file */
13433 int
13434 SavePositionToFile (char *filename)
13435 {
13436     FILE *f;
13437     char buf[MSG_SIZ];
13438
13439     if (strcmp(filename, "-") == 0) {
13440         return SavePosition(stdout, 0, NULL);
13441     } else {
13442         f = fopen(filename, "a");
13443         if (f == NULL) {
13444             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13445             DisplayError(buf, errno);
13446             return FALSE;
13447         } else {
13448             safeStrCpy(buf, lastMsg, MSG_SIZ);
13449             DisplayMessage(_("Waiting for access to save file"), "");
13450             flock(fileno(f), LOCK_EX); // [HGM] lock
13451             DisplayMessage(_("Saving position"), "");
13452             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13453             SavePosition(f, 0, NULL);
13454             DisplayMessage(buf, "");
13455             return TRUE;
13456         }
13457     }
13458 }
13459
13460 /* Save the current position to the given open file and close the file */
13461 int
13462 SavePosition (FILE *f, int dummy, char *dummy2)
13463 {
13464     time_t tm;
13465     char *fen;
13466
13467     if (gameMode == EditPosition) EditPositionDone(TRUE);
13468     if (appData.oldSaveStyle) {
13469         tm = time((time_t *) NULL);
13470
13471         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13472         PrintOpponents(f);
13473         fprintf(f, "[--------------\n");
13474         PrintPosition(f, currentMove);
13475         fprintf(f, "--------------]\n");
13476     } else {
13477         fen = PositionToFEN(currentMove, NULL, 1);
13478         fprintf(f, "%s\n", fen);
13479         free(fen);
13480     }
13481     fclose(f);
13482     return TRUE;
13483 }
13484
13485 void
13486 ReloadCmailMsgEvent (int unregister)
13487 {
13488 #if !WIN32
13489     static char *inFilename = NULL;
13490     static char *outFilename;
13491     int i;
13492     struct stat inbuf, outbuf;
13493     int status;
13494
13495     /* Any registered moves are unregistered if unregister is set, */
13496     /* i.e. invoked by the signal handler */
13497     if (unregister) {
13498         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13499             cmailMoveRegistered[i] = FALSE;
13500             if (cmailCommentList[i] != NULL) {
13501                 free(cmailCommentList[i]);
13502                 cmailCommentList[i] = NULL;
13503             }
13504         }
13505         nCmailMovesRegistered = 0;
13506     }
13507
13508     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13509         cmailResult[i] = CMAIL_NOT_RESULT;
13510     }
13511     nCmailResults = 0;
13512
13513     if (inFilename == NULL) {
13514         /* Because the filenames are static they only get malloced once  */
13515         /* and they never get freed                                      */
13516         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13517         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13518
13519         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13520         sprintf(outFilename, "%s.out", appData.cmailGameName);
13521     }
13522
13523     status = stat(outFilename, &outbuf);
13524     if (status < 0) {
13525         cmailMailedMove = FALSE;
13526     } else {
13527         status = stat(inFilename, &inbuf);
13528         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13529     }
13530
13531     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13532        counts the games, notes how each one terminated, etc.
13533
13534        It would be nice to remove this kludge and instead gather all
13535        the information while building the game list.  (And to keep it
13536        in the game list nodes instead of having a bunch of fixed-size
13537        parallel arrays.)  Note this will require getting each game's
13538        termination from the PGN tags, as the game list builder does
13539        not process the game moves.  --mann
13540        */
13541     cmailMsgLoaded = TRUE;
13542     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13543
13544     /* Load first game in the file or popup game menu */
13545     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13546
13547 #endif /* !WIN32 */
13548     return;
13549 }
13550
13551 int
13552 RegisterMove ()
13553 {
13554     FILE *f;
13555     char string[MSG_SIZ];
13556
13557     if (   cmailMailedMove
13558         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13559         return TRUE;            /* Allow free viewing  */
13560     }
13561
13562     /* Unregister move to ensure that we don't leave RegisterMove        */
13563     /* with the move registered when the conditions for registering no   */
13564     /* longer hold                                                       */
13565     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13566         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13567         nCmailMovesRegistered --;
13568
13569         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13570           {
13571               free(cmailCommentList[lastLoadGameNumber - 1]);
13572               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13573           }
13574     }
13575
13576     if (cmailOldMove == -1) {
13577         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13578         return FALSE;
13579     }
13580
13581     if (currentMove > cmailOldMove + 1) {
13582         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13583         return FALSE;
13584     }
13585
13586     if (currentMove < cmailOldMove) {
13587         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13588         return FALSE;
13589     }
13590
13591     if (forwardMostMove > currentMove) {
13592         /* Silently truncate extra moves */
13593         TruncateGame();
13594     }
13595
13596     if (   (currentMove == cmailOldMove + 1)
13597         || (   (currentMove == cmailOldMove)
13598             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13599                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13600         if (gameInfo.result != GameUnfinished) {
13601             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13602         }
13603
13604         if (commentList[currentMove] != NULL) {
13605             cmailCommentList[lastLoadGameNumber - 1]
13606               = StrSave(commentList[currentMove]);
13607         }
13608         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13609
13610         if (appData.debugMode)
13611           fprintf(debugFP, "Saving %s for game %d\n",
13612                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13613
13614         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13615
13616         f = fopen(string, "w");
13617         if (appData.oldSaveStyle) {
13618             SaveGameOldStyle(f); /* also closes the file */
13619
13620             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13621             f = fopen(string, "w");
13622             SavePosition(f, 0, NULL); /* also closes the file */
13623         } else {
13624             fprintf(f, "{--------------\n");
13625             PrintPosition(f, currentMove);
13626             fprintf(f, "--------------}\n\n");
13627
13628             SaveGame(f, 0, NULL); /* also closes the file*/
13629         }
13630
13631         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13632         nCmailMovesRegistered ++;
13633     } else if (nCmailGames == 1) {
13634         DisplayError(_("You have not made a move yet"), 0);
13635         return FALSE;
13636     }
13637
13638     return TRUE;
13639 }
13640
13641 void
13642 MailMoveEvent ()
13643 {
13644 #if !WIN32
13645     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13646     FILE *commandOutput;
13647     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13648     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13649     int nBuffers;
13650     int i;
13651     int archived;
13652     char *arcDir;
13653
13654     if (! cmailMsgLoaded) {
13655         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13656         return;
13657     }
13658
13659     if (nCmailGames == nCmailResults) {
13660         DisplayError(_("No unfinished games"), 0);
13661         return;
13662     }
13663
13664 #if CMAIL_PROHIBIT_REMAIL
13665     if (cmailMailedMove) {
13666       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);
13667         DisplayError(msg, 0);
13668         return;
13669     }
13670 #endif
13671
13672     if (! (cmailMailedMove || RegisterMove())) return;
13673
13674     if (   cmailMailedMove
13675         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13676       snprintf(string, MSG_SIZ, partCommandString,
13677                appData.debugMode ? " -v" : "", appData.cmailGameName);
13678         commandOutput = popen(string, "r");
13679
13680         if (commandOutput == NULL) {
13681             DisplayError(_("Failed to invoke cmail"), 0);
13682         } else {
13683             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13684                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13685             }
13686             if (nBuffers > 1) {
13687                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13688                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13689                 nBytes = MSG_SIZ - 1;
13690             } else {
13691                 (void) memcpy(msg, buffer, nBytes);
13692             }
13693             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13694
13695             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13696                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13697
13698                 archived = TRUE;
13699                 for (i = 0; i < nCmailGames; i ++) {
13700                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13701                         archived = FALSE;
13702                     }
13703                 }
13704                 if (   archived
13705                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13706                         != NULL)) {
13707                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13708                            arcDir,
13709                            appData.cmailGameName,
13710                            gameInfo.date);
13711                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13712                     cmailMsgLoaded = FALSE;
13713                 }
13714             }
13715
13716             DisplayInformation(msg);
13717             pclose(commandOutput);
13718         }
13719     } else {
13720         if ((*cmailMsg) != '\0') {
13721             DisplayInformation(cmailMsg);
13722         }
13723     }
13724
13725     return;
13726 #endif /* !WIN32 */
13727 }
13728
13729 char *
13730 CmailMsg ()
13731 {
13732 #if WIN32
13733     return NULL;
13734 #else
13735     int  prependComma = 0;
13736     char number[5];
13737     char string[MSG_SIZ];       /* Space for game-list */
13738     int  i;
13739
13740     if (!cmailMsgLoaded) return "";
13741
13742     if (cmailMailedMove) {
13743       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13744     } else {
13745         /* Create a list of games left */
13746       snprintf(string, MSG_SIZ, "[");
13747         for (i = 0; i < nCmailGames; i ++) {
13748             if (! (   cmailMoveRegistered[i]
13749                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13750                 if (prependComma) {
13751                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13752                 } else {
13753                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13754                     prependComma = 1;
13755                 }
13756
13757                 strcat(string, number);
13758             }
13759         }
13760         strcat(string, "]");
13761
13762         if (nCmailMovesRegistered + nCmailResults == 0) {
13763             switch (nCmailGames) {
13764               case 1:
13765                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13766                 break;
13767
13768               case 2:
13769                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13770                 break;
13771
13772               default:
13773                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13774                          nCmailGames);
13775                 break;
13776             }
13777         } else {
13778             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13779               case 1:
13780                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13781                          string);
13782                 break;
13783
13784               case 0:
13785                 if (nCmailResults == nCmailGames) {
13786                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13787                 } else {
13788                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13789                 }
13790                 break;
13791
13792               default:
13793                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13794                          string);
13795             }
13796         }
13797     }
13798     return cmailMsg;
13799 #endif /* WIN32 */
13800 }
13801
13802 void
13803 ResetGameEvent ()
13804 {
13805     if (gameMode == Training)
13806       SetTrainingModeOff();
13807
13808     Reset(TRUE, TRUE);
13809     cmailMsgLoaded = FALSE;
13810     if (appData.icsActive) {
13811       SendToICS(ics_prefix);
13812       SendToICS("refresh\n");
13813     }
13814 }
13815
13816 void
13817 ExitEvent (int status)
13818 {
13819     exiting++;
13820     if (exiting > 2) {
13821       /* Give up on clean exit */
13822       exit(status);
13823     }
13824     if (exiting > 1) {
13825       /* Keep trying for clean exit */
13826       return;
13827     }
13828
13829     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13830
13831     if (telnetISR != NULL) {
13832       RemoveInputSource(telnetISR);
13833     }
13834     if (icsPR != NoProc) {
13835       DestroyChildProcess(icsPR, TRUE);
13836     }
13837
13838     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13839     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13840
13841     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13842     /* make sure this other one finishes before killing it!                  */
13843     if(endingGame) { int count = 0;
13844         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13845         while(endingGame && count++ < 10) DoSleep(1);
13846         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13847     }
13848
13849     /* Kill off chess programs */
13850     if (first.pr != NoProc) {
13851         ExitAnalyzeMode();
13852
13853         DoSleep( appData.delayBeforeQuit );
13854         SendToProgram("quit\n", &first);
13855         DoSleep( appData.delayAfterQuit );
13856         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13857     }
13858     if (second.pr != NoProc) {
13859         DoSleep( appData.delayBeforeQuit );
13860         SendToProgram("quit\n", &second);
13861         DoSleep( appData.delayAfterQuit );
13862         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13863     }
13864     if (first.isr != NULL) {
13865         RemoveInputSource(first.isr);
13866     }
13867     if (second.isr != NULL) {
13868         RemoveInputSource(second.isr);
13869     }
13870
13871     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13872     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13873
13874     ShutDownFrontEnd();
13875     exit(status);
13876 }
13877
13878 void
13879 PauseEngine (ChessProgramState *cps)
13880 {
13881     SendToProgram("pause\n", cps);
13882     cps->pause = 2;
13883 }
13884
13885 void
13886 UnPauseEngine (ChessProgramState *cps)
13887 {
13888     SendToProgram("resume\n", cps);
13889     cps->pause = 1;
13890 }
13891
13892 void
13893 PauseEvent ()
13894 {
13895     if (appData.debugMode)
13896         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13897     if (pausing) {
13898         pausing = FALSE;
13899         ModeHighlight();
13900         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13901             StartClocks();
13902             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13903                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13904                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13905             }
13906             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13907             HandleMachineMove(stashedInputMove, stalledEngine);
13908             stalledEngine = NULL;
13909             return;
13910         }
13911         if (gameMode == MachinePlaysWhite ||
13912             gameMode == TwoMachinesPlay   ||
13913             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13914             if(first.pause)  UnPauseEngine(&first);
13915             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13916             if(second.pause) UnPauseEngine(&second);
13917             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13918             StartClocks();
13919         } else {
13920             DisplayBothClocks();
13921         }
13922         if (gameMode == PlayFromGameFile) {
13923             if (appData.timeDelay >= 0)
13924                 AutoPlayGameLoop();
13925         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13926             Reset(FALSE, TRUE);
13927             SendToICS(ics_prefix);
13928             SendToICS("refresh\n");
13929         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13930             ForwardInner(forwardMostMove);
13931         }
13932         pauseExamInvalid = FALSE;
13933     } else {
13934         switch (gameMode) {
13935           default:
13936             return;
13937           case IcsExamining:
13938             pauseExamForwardMostMove = forwardMostMove;
13939             pauseExamInvalid = FALSE;
13940             /* fall through */
13941           case IcsObserving:
13942           case IcsPlayingWhite:
13943           case IcsPlayingBlack:
13944             pausing = TRUE;
13945             ModeHighlight();
13946             return;
13947           case PlayFromGameFile:
13948             (void) StopLoadGameTimer();
13949             pausing = TRUE;
13950             ModeHighlight();
13951             break;
13952           case BeginningOfGame:
13953             if (appData.icsActive) return;
13954             /* else fall through */
13955           case MachinePlaysWhite:
13956           case MachinePlaysBlack:
13957           case TwoMachinesPlay:
13958             if (forwardMostMove == 0)
13959               return;           /* don't pause if no one has moved */
13960             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13961                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13962                 if(onMove->pause) {           // thinking engine can be paused
13963                     PauseEngine(onMove);      // do it
13964                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13965                         PauseEngine(onMove->other);
13966                     else
13967                         SendToProgram("easy\n", onMove->other);
13968                     StopClocks();
13969                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13970             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13971                 if(first.pause) {
13972                     PauseEngine(&first);
13973                     StopClocks();
13974                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13975             } else { // human on move, pause pondering by either method
13976                 if(first.pause)
13977                     PauseEngine(&first);
13978                 else if(appData.ponderNextMove)
13979                     SendToProgram("easy\n", &first);
13980                 StopClocks();
13981             }
13982             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13983           case AnalyzeMode:
13984             pausing = TRUE;
13985             ModeHighlight();
13986             break;
13987         }
13988     }
13989 }
13990
13991 void
13992 EditCommentEvent ()
13993 {
13994     char title[MSG_SIZ];
13995
13996     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13997       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13998     } else {
13999       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14000                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14001                parseList[currentMove - 1]);
14002     }
14003
14004     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14005 }
14006
14007
14008 void
14009 EditTagsEvent ()
14010 {
14011     char *tags = PGNTags(&gameInfo);
14012     bookUp = FALSE;
14013     EditTagsPopUp(tags, NULL);
14014     free(tags);
14015 }
14016
14017 void
14018 ToggleSecond ()
14019 {
14020   if(second.analyzing) {
14021     SendToProgram("exit\n", &second);
14022     second.analyzing = FALSE;
14023   } else {
14024     if (second.pr == NoProc) StartChessProgram(&second);
14025     InitChessProgram(&second, FALSE);
14026     FeedMovesToProgram(&second, currentMove);
14027
14028     SendToProgram("analyze\n", &second);
14029     second.analyzing = TRUE;
14030   }
14031 }
14032
14033 /* Toggle ShowThinking */
14034 void
14035 ToggleShowThinking()
14036 {
14037   appData.showThinking = !appData.showThinking;
14038   ShowThinkingEvent();
14039 }
14040
14041 int
14042 AnalyzeModeEvent ()
14043 {
14044     char buf[MSG_SIZ];
14045
14046     if (!first.analysisSupport) {
14047       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14048       DisplayError(buf, 0);
14049       return 0;
14050     }
14051     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14052     if (appData.icsActive) {
14053         if (gameMode != IcsObserving) {
14054           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14055             DisplayError(buf, 0);
14056             /* secure check */
14057             if (appData.icsEngineAnalyze) {
14058                 if (appData.debugMode)
14059                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14060                 ExitAnalyzeMode();
14061                 ModeHighlight();
14062             }
14063             return 0;
14064         }
14065         /* if enable, user wants to disable icsEngineAnalyze */
14066         if (appData.icsEngineAnalyze) {
14067                 ExitAnalyzeMode();
14068                 ModeHighlight();
14069                 return 0;
14070         }
14071         appData.icsEngineAnalyze = TRUE;
14072         if (appData.debugMode)
14073             fprintf(debugFP, "ICS engine analyze starting... \n");
14074     }
14075
14076     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14077     if (appData.noChessProgram || gameMode == AnalyzeMode)
14078       return 0;
14079
14080     if (gameMode != AnalyzeFile) {
14081         if (!appData.icsEngineAnalyze) {
14082                EditGameEvent();
14083                if (gameMode != EditGame) return 0;
14084         }
14085         if (!appData.showThinking) ToggleShowThinking();
14086         ResurrectChessProgram();
14087         SendToProgram("analyze\n", &first);
14088         first.analyzing = TRUE;
14089         /*first.maybeThinking = TRUE;*/
14090         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14091         EngineOutputPopUp();
14092     }
14093     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14094     pausing = FALSE;
14095     ModeHighlight();
14096     SetGameInfo();
14097
14098     StartAnalysisClock();
14099     GetTimeMark(&lastNodeCountTime);
14100     lastNodeCount = 0;
14101     return 1;
14102 }
14103
14104 void
14105 AnalyzeFileEvent ()
14106 {
14107     if (appData.noChessProgram || gameMode == AnalyzeFile)
14108       return;
14109
14110     if (!first.analysisSupport) {
14111       char buf[MSG_SIZ];
14112       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14113       DisplayError(buf, 0);
14114       return;
14115     }
14116
14117     if (gameMode != AnalyzeMode) {
14118         keepInfo = 1; // mere annotating should not alter PGN tags
14119         EditGameEvent();
14120         keepInfo = 0;
14121         if (gameMode != EditGame) return;
14122         if (!appData.showThinking) ToggleShowThinking();
14123         ResurrectChessProgram();
14124         SendToProgram("analyze\n", &first);
14125         first.analyzing = TRUE;
14126         /*first.maybeThinking = TRUE;*/
14127         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14128         EngineOutputPopUp();
14129     }
14130     gameMode = AnalyzeFile;
14131     pausing = FALSE;
14132     ModeHighlight();
14133
14134     StartAnalysisClock();
14135     GetTimeMark(&lastNodeCountTime);
14136     lastNodeCount = 0;
14137     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14138     AnalysisPeriodicEvent(1);
14139 }
14140
14141 void
14142 MachineWhiteEvent ()
14143 {
14144     char buf[MSG_SIZ];
14145     char *bookHit = NULL;
14146
14147     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14148       return;
14149
14150
14151     if (gameMode == PlayFromGameFile ||
14152         gameMode == TwoMachinesPlay  ||
14153         gameMode == Training         ||
14154         gameMode == AnalyzeMode      ||
14155         gameMode == EndOfGame)
14156         EditGameEvent();
14157
14158     if (gameMode == EditPosition)
14159         EditPositionDone(TRUE);
14160
14161     if (!WhiteOnMove(currentMove)) {
14162         DisplayError(_("It is not White's turn"), 0);
14163         return;
14164     }
14165
14166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14167       ExitAnalyzeMode();
14168
14169     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14170         gameMode == AnalyzeFile)
14171         TruncateGame();
14172
14173     ResurrectChessProgram();    /* in case it isn't running */
14174     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14175         gameMode = MachinePlaysWhite;
14176         ResetClocks();
14177     } else
14178     gameMode = MachinePlaysWhite;
14179     pausing = FALSE;
14180     ModeHighlight();
14181     SetGameInfo();
14182     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14183     DisplayTitle(buf);
14184     if (first.sendName) {
14185       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14186       SendToProgram(buf, &first);
14187     }
14188     if (first.sendTime) {
14189       if (first.useColors) {
14190         SendToProgram("black\n", &first); /*gnu kludge*/
14191       }
14192       SendTimeRemaining(&first, TRUE);
14193     }
14194     if (first.useColors) {
14195       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14196     }
14197     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14198     SetMachineThinkingEnables();
14199     first.maybeThinking = TRUE;
14200     StartClocks();
14201     firstMove = FALSE;
14202
14203     if (appData.autoFlipView && !flipView) {
14204       flipView = !flipView;
14205       DrawPosition(FALSE, NULL);
14206       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14207     }
14208
14209     if(bookHit) { // [HGM] book: simulate book reply
14210         static char bookMove[MSG_SIZ]; // a bit generous?
14211
14212         programStats.nodes = programStats.depth = programStats.time =
14213         programStats.score = programStats.got_only_move = 0;
14214         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14215
14216         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14217         strcat(bookMove, bookHit);
14218         HandleMachineMove(bookMove, &first);
14219     }
14220 }
14221
14222 void
14223 MachineBlackEvent ()
14224 {
14225   char buf[MSG_SIZ];
14226   char *bookHit = NULL;
14227
14228     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14229         return;
14230
14231
14232     if (gameMode == PlayFromGameFile ||
14233         gameMode == TwoMachinesPlay  ||
14234         gameMode == Training         ||
14235         gameMode == AnalyzeMode      ||
14236         gameMode == EndOfGame)
14237         EditGameEvent();
14238
14239     if (gameMode == EditPosition)
14240         EditPositionDone(TRUE);
14241
14242     if (WhiteOnMove(currentMove)) {
14243         DisplayError(_("It is not Black's turn"), 0);
14244         return;
14245     }
14246
14247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14248       ExitAnalyzeMode();
14249
14250     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14251         gameMode == AnalyzeFile)
14252         TruncateGame();
14253
14254     ResurrectChessProgram();    /* in case it isn't running */
14255     gameMode = MachinePlaysBlack;
14256     pausing = FALSE;
14257     ModeHighlight();
14258     SetGameInfo();
14259     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14260     DisplayTitle(buf);
14261     if (first.sendName) {
14262       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14263       SendToProgram(buf, &first);
14264     }
14265     if (first.sendTime) {
14266       if (first.useColors) {
14267         SendToProgram("white\n", &first); /*gnu kludge*/
14268       }
14269       SendTimeRemaining(&first, FALSE);
14270     }
14271     if (first.useColors) {
14272       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14273     }
14274     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14275     SetMachineThinkingEnables();
14276     first.maybeThinking = TRUE;
14277     StartClocks();
14278
14279     if (appData.autoFlipView && flipView) {
14280       flipView = !flipView;
14281       DrawPosition(FALSE, NULL);
14282       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14283     }
14284     if(bookHit) { // [HGM] book: simulate book reply
14285         static char bookMove[MSG_SIZ]; // a bit generous?
14286
14287         programStats.nodes = programStats.depth = programStats.time =
14288         programStats.score = programStats.got_only_move = 0;
14289         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14290
14291         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14292         strcat(bookMove, bookHit);
14293         HandleMachineMove(bookMove, &first);
14294     }
14295 }
14296
14297
14298 void
14299 DisplayTwoMachinesTitle ()
14300 {
14301     char buf[MSG_SIZ];
14302     if (appData.matchGames > 0) {
14303         if(appData.tourneyFile[0]) {
14304           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14305                    gameInfo.white, _("vs."), gameInfo.black,
14306                    nextGame+1, appData.matchGames+1,
14307                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14308         } else
14309         if (first.twoMachinesColor[0] == 'w') {
14310           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14311                    gameInfo.white, _("vs."),  gameInfo.black,
14312                    first.matchWins, second.matchWins,
14313                    matchGame - 1 - (first.matchWins + second.matchWins));
14314         } else {
14315           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14316                    gameInfo.white, _("vs."), gameInfo.black,
14317                    second.matchWins, first.matchWins,
14318                    matchGame - 1 - (first.matchWins + second.matchWins));
14319         }
14320     } else {
14321       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14322     }
14323     DisplayTitle(buf);
14324 }
14325
14326 void
14327 SettingsMenuIfReady ()
14328 {
14329   if (second.lastPing != second.lastPong) {
14330     DisplayMessage("", _("Waiting for second chess program"));
14331     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14332     return;
14333   }
14334   ThawUI();
14335   DisplayMessage("", "");
14336   SettingsPopUp(&second);
14337 }
14338
14339 int
14340 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14341 {
14342     char buf[MSG_SIZ];
14343     if (cps->pr == NoProc) {
14344         StartChessProgram(cps);
14345         if (cps->protocolVersion == 1) {
14346           retry();
14347           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14348         } else {
14349           /* kludge: allow timeout for initial "feature" command */
14350           if(retry != TwoMachinesEventIfReady) FreezeUI();
14351           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14352           DisplayMessage("", buf);
14353           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14354         }
14355         return 1;
14356     }
14357     return 0;
14358 }
14359
14360 void
14361 TwoMachinesEvent P((void))
14362 {
14363     int i;
14364     char buf[MSG_SIZ];
14365     ChessProgramState *onmove;
14366     char *bookHit = NULL;
14367     static int stalling = 0;
14368     TimeMark now;
14369     long wait;
14370
14371     if (appData.noChessProgram) return;
14372
14373     switch (gameMode) {
14374       case TwoMachinesPlay:
14375         return;
14376       case MachinePlaysWhite:
14377       case MachinePlaysBlack:
14378         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14379             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14380             return;
14381         }
14382         /* fall through */
14383       case BeginningOfGame:
14384       case PlayFromGameFile:
14385       case EndOfGame:
14386         EditGameEvent();
14387         if (gameMode != EditGame) return;
14388         break;
14389       case EditPosition:
14390         EditPositionDone(TRUE);
14391         break;
14392       case AnalyzeMode:
14393       case AnalyzeFile:
14394         ExitAnalyzeMode();
14395         break;
14396       case EditGame:
14397       default:
14398         break;
14399     }
14400
14401 //    forwardMostMove = currentMove;
14402     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14403     startingEngine = TRUE;
14404
14405     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14406
14407     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14408     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14409       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14410       return;
14411     }
14412     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14413
14414     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14415                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14416         startingEngine = FALSE;
14417         DisplayError("second engine does not play this", 0);
14418         return;
14419     }
14420
14421     if(!stalling) {
14422       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14423       SendToProgram("force\n", &second);
14424       stalling = 1;
14425       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14426       return;
14427     }
14428     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14429     if(appData.matchPause>10000 || appData.matchPause<10)
14430                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14431     wait = SubtractTimeMarks(&now, &pauseStart);
14432     if(wait < appData.matchPause) {
14433         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14434         return;
14435     }
14436     // we are now committed to starting the game
14437     stalling = 0;
14438     DisplayMessage("", "");
14439     if (startedFromSetupPosition) {
14440         SendBoard(&second, backwardMostMove);
14441     if (appData.debugMode) {
14442         fprintf(debugFP, "Two Machines\n");
14443     }
14444     }
14445     for (i = backwardMostMove; i < forwardMostMove; i++) {
14446         SendMoveToProgram(i, &second);
14447     }
14448
14449     gameMode = TwoMachinesPlay;
14450     pausing = startingEngine = FALSE;
14451     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14452     SetGameInfo();
14453     DisplayTwoMachinesTitle();
14454     firstMove = TRUE;
14455     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14456         onmove = &first;
14457     } else {
14458         onmove = &second;
14459     }
14460     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14461     SendToProgram(first.computerString, &first);
14462     if (first.sendName) {
14463       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14464       SendToProgram(buf, &first);
14465     }
14466     SendToProgram(second.computerString, &second);
14467     if (second.sendName) {
14468       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14469       SendToProgram(buf, &second);
14470     }
14471
14472     ResetClocks();
14473     if (!first.sendTime || !second.sendTime) {
14474         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14475         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14476     }
14477     if (onmove->sendTime) {
14478       if (onmove->useColors) {
14479         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14480       }
14481       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14482     }
14483     if (onmove->useColors) {
14484       SendToProgram(onmove->twoMachinesColor, onmove);
14485     }
14486     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14487 //    SendToProgram("go\n", onmove);
14488     onmove->maybeThinking = TRUE;
14489     SetMachineThinkingEnables();
14490
14491     StartClocks();
14492
14493     if(bookHit) { // [HGM] book: simulate book reply
14494         static char bookMove[MSG_SIZ]; // a bit generous?
14495
14496         programStats.nodes = programStats.depth = programStats.time =
14497         programStats.score = programStats.got_only_move = 0;
14498         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14499
14500         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14501         strcat(bookMove, bookHit);
14502         savedMessage = bookMove; // args for deferred call
14503         savedState = onmove;
14504         ScheduleDelayedEvent(DeferredBookMove, 1);
14505     }
14506 }
14507
14508 void
14509 TrainingEvent ()
14510 {
14511     if (gameMode == Training) {
14512       SetTrainingModeOff();
14513       gameMode = PlayFromGameFile;
14514       DisplayMessage("", _("Training mode off"));
14515     } else {
14516       gameMode = Training;
14517       animateTraining = appData.animate;
14518
14519       /* make sure we are not already at the end of the game */
14520       if (currentMove < forwardMostMove) {
14521         SetTrainingModeOn();
14522         DisplayMessage("", _("Training mode on"));
14523       } else {
14524         gameMode = PlayFromGameFile;
14525         DisplayError(_("Already at end of game"), 0);
14526       }
14527     }
14528     ModeHighlight();
14529 }
14530
14531 void
14532 IcsClientEvent ()
14533 {
14534     if (!appData.icsActive) return;
14535     switch (gameMode) {
14536       case IcsPlayingWhite:
14537       case IcsPlayingBlack:
14538       case IcsObserving:
14539       case IcsIdle:
14540       case BeginningOfGame:
14541       case IcsExamining:
14542         return;
14543
14544       case EditGame:
14545         break;
14546
14547       case EditPosition:
14548         EditPositionDone(TRUE);
14549         break;
14550
14551       case AnalyzeMode:
14552       case AnalyzeFile:
14553         ExitAnalyzeMode();
14554         break;
14555
14556       default:
14557         EditGameEvent();
14558         break;
14559     }
14560
14561     gameMode = IcsIdle;
14562     ModeHighlight();
14563     return;
14564 }
14565
14566 void
14567 EditGameEvent ()
14568 {
14569     int i;
14570
14571     switch (gameMode) {
14572       case Training:
14573         SetTrainingModeOff();
14574         break;
14575       case MachinePlaysWhite:
14576       case MachinePlaysBlack:
14577       case BeginningOfGame:
14578         SendToProgram("force\n", &first);
14579         SetUserThinkingEnables();
14580         break;
14581       case PlayFromGameFile:
14582         (void) StopLoadGameTimer();
14583         if (gameFileFP != NULL) {
14584             gameFileFP = NULL;
14585         }
14586         break;
14587       case EditPosition:
14588         EditPositionDone(TRUE);
14589         break;
14590       case AnalyzeMode:
14591       case AnalyzeFile:
14592         ExitAnalyzeMode();
14593         SendToProgram("force\n", &first);
14594         break;
14595       case TwoMachinesPlay:
14596         GameEnds(EndOfFile, NULL, GE_PLAYER);
14597         ResurrectChessProgram();
14598         SetUserThinkingEnables();
14599         break;
14600       case EndOfGame:
14601         ResurrectChessProgram();
14602         break;
14603       case IcsPlayingBlack:
14604       case IcsPlayingWhite:
14605         DisplayError(_("Warning: You are still playing a game"), 0);
14606         break;
14607       case IcsObserving:
14608         DisplayError(_("Warning: You are still observing a game"), 0);
14609         break;
14610       case IcsExamining:
14611         DisplayError(_("Warning: You are still examining a game"), 0);
14612         break;
14613       case IcsIdle:
14614         break;
14615       case EditGame:
14616       default:
14617         return;
14618     }
14619
14620     pausing = FALSE;
14621     StopClocks();
14622     first.offeredDraw = second.offeredDraw = 0;
14623
14624     if (gameMode == PlayFromGameFile) {
14625         whiteTimeRemaining = timeRemaining[0][currentMove];
14626         blackTimeRemaining = timeRemaining[1][currentMove];
14627         DisplayTitle("");
14628     }
14629
14630     if (gameMode == MachinePlaysWhite ||
14631         gameMode == MachinePlaysBlack ||
14632         gameMode == TwoMachinesPlay ||
14633         gameMode == EndOfGame) {
14634         i = forwardMostMove;
14635         while (i > currentMove) {
14636             SendToProgram("undo\n", &first);
14637             i--;
14638         }
14639         if(!adjustedClock) {
14640         whiteTimeRemaining = timeRemaining[0][currentMove];
14641         blackTimeRemaining = timeRemaining[1][currentMove];
14642         DisplayBothClocks();
14643         }
14644         if (whiteFlag || blackFlag) {
14645             whiteFlag = blackFlag = 0;
14646         }
14647         DisplayTitle("");
14648     }
14649
14650     gameMode = EditGame;
14651     ModeHighlight();
14652     SetGameInfo();
14653 }
14654
14655
14656 void
14657 EditPositionEvent ()
14658 {
14659     if (gameMode == EditPosition) {
14660         EditGameEvent();
14661         return;
14662     }
14663
14664     EditGameEvent();
14665     if (gameMode != EditGame) return;
14666
14667     gameMode = EditPosition;
14668     ModeHighlight();
14669     SetGameInfo();
14670     if (currentMove > 0)
14671       CopyBoard(boards[0], boards[currentMove]);
14672
14673     blackPlaysFirst = !WhiteOnMove(currentMove);
14674     ResetClocks();
14675     currentMove = forwardMostMove = backwardMostMove = 0;
14676     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14677     DisplayMove(-1);
14678     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14679 }
14680
14681 void
14682 ExitAnalyzeMode ()
14683 {
14684     /* [DM] icsEngineAnalyze - possible call from other functions */
14685     if (appData.icsEngineAnalyze) {
14686         appData.icsEngineAnalyze = FALSE;
14687
14688         DisplayMessage("",_("Close ICS engine analyze..."));
14689     }
14690     if (first.analysisSupport && first.analyzing) {
14691       SendToBoth("exit\n");
14692       first.analyzing = second.analyzing = FALSE;
14693     }
14694     thinkOutput[0] = NULLCHAR;
14695 }
14696
14697 void
14698 EditPositionDone (Boolean fakeRights)
14699 {
14700     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14701
14702     startedFromSetupPosition = TRUE;
14703     InitChessProgram(&first, FALSE);
14704     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14705       boards[0][EP_STATUS] = EP_NONE;
14706       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14707       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14708         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14709         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14710       } else boards[0][CASTLING][2] = NoRights;
14711       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14712         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14713         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14714       } else boards[0][CASTLING][5] = NoRights;
14715       if(gameInfo.variant == VariantSChess) {
14716         int i;
14717         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14718           boards[0][VIRGIN][i] = 0;
14719           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14720           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14721         }
14722       }
14723     }
14724     SendToProgram("force\n", &first);
14725     if (blackPlaysFirst) {
14726         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14727         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14728         currentMove = forwardMostMove = backwardMostMove = 1;
14729         CopyBoard(boards[1], boards[0]);
14730     } else {
14731         currentMove = forwardMostMove = backwardMostMove = 0;
14732     }
14733     SendBoard(&first, forwardMostMove);
14734     if (appData.debugMode) {
14735         fprintf(debugFP, "EditPosDone\n");
14736     }
14737     DisplayTitle("");
14738     DisplayMessage("", "");
14739     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14740     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14741     gameMode = EditGame;
14742     ModeHighlight();
14743     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14744     ClearHighlights(); /* [AS] */
14745 }
14746
14747 /* Pause for `ms' milliseconds */
14748 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14749 void
14750 TimeDelay (long ms)
14751 {
14752     TimeMark m1, m2;
14753
14754     GetTimeMark(&m1);
14755     do {
14756         GetTimeMark(&m2);
14757     } while (SubtractTimeMarks(&m2, &m1) < ms);
14758 }
14759
14760 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14761 void
14762 SendMultiLineToICS (char *buf)
14763 {
14764     char temp[MSG_SIZ+1], *p;
14765     int len;
14766
14767     len = strlen(buf);
14768     if (len > MSG_SIZ)
14769       len = MSG_SIZ;
14770
14771     strncpy(temp, buf, len);
14772     temp[len] = 0;
14773
14774     p = temp;
14775     while (*p) {
14776         if (*p == '\n' || *p == '\r')
14777           *p = ' ';
14778         ++p;
14779     }
14780
14781     strcat(temp, "\n");
14782     SendToICS(temp);
14783     SendToPlayer(temp, strlen(temp));
14784 }
14785
14786 void
14787 SetWhiteToPlayEvent ()
14788 {
14789     if (gameMode == EditPosition) {
14790         blackPlaysFirst = FALSE;
14791         DisplayBothClocks();    /* works because currentMove is 0 */
14792     } else if (gameMode == IcsExamining) {
14793         SendToICS(ics_prefix);
14794         SendToICS("tomove white\n");
14795     }
14796 }
14797
14798 void
14799 SetBlackToPlayEvent ()
14800 {
14801     if (gameMode == EditPosition) {
14802         blackPlaysFirst = TRUE;
14803         currentMove = 1;        /* kludge */
14804         DisplayBothClocks();
14805         currentMove = 0;
14806     } else if (gameMode == IcsExamining) {
14807         SendToICS(ics_prefix);
14808         SendToICS("tomove black\n");
14809     }
14810 }
14811
14812 void
14813 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14814 {
14815     char buf[MSG_SIZ];
14816     ChessSquare piece = boards[0][y][x];
14817     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14818     static int lastVariant;
14819
14820     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14821
14822     switch (selection) {
14823       case ClearBoard:
14824         CopyBoard(currentBoard, boards[0]);
14825         CopyBoard(menuBoard, initialPosition);
14826         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14827             SendToICS(ics_prefix);
14828             SendToICS("bsetup clear\n");
14829         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14830             SendToICS(ics_prefix);
14831             SendToICS("clearboard\n");
14832         } else {
14833             int nonEmpty = 0;
14834             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14835                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14836                 for (y = 0; y < BOARD_HEIGHT; y++) {
14837                     if (gameMode == IcsExamining) {
14838                         if (boards[currentMove][y][x] != EmptySquare) {
14839                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14840                                     AAA + x, ONE + y);
14841                             SendToICS(buf);
14842                         }
14843                     } else {
14844                         if(boards[0][y][x] != p) nonEmpty++;
14845                         boards[0][y][x] = p;
14846                     }
14847                 }
14848                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14849             }
14850             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14851                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14852                     ChessSquare p = menuBoard[0][x];
14853                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14854                     p = menuBoard[BOARD_HEIGHT-1][x];
14855                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14856                 }
14857                 DisplayMessage("Clicking clock again restores position", "");
14858                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14859                 if(!nonEmpty) { // asked to clear an empty board
14860                     CopyBoard(boards[0], menuBoard);
14861                 } else
14862                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14863                     CopyBoard(boards[0], initialPosition);
14864                 } else
14865                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14866                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14867                     CopyBoard(boards[0], erasedBoard);
14868                 } else
14869                     CopyBoard(erasedBoard, currentBoard);
14870
14871             }
14872         }
14873         if (gameMode == EditPosition) {
14874             DrawPosition(FALSE, boards[0]);
14875         }
14876         break;
14877
14878       case WhitePlay:
14879         SetWhiteToPlayEvent();
14880         break;
14881
14882       case BlackPlay:
14883         SetBlackToPlayEvent();
14884         break;
14885
14886       case EmptySquare:
14887         if (gameMode == IcsExamining) {
14888             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14889             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14890             SendToICS(buf);
14891         } else {
14892             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14893                 if(x == BOARD_LEFT-2) {
14894                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14895                     boards[0][y][1] = 0;
14896                 } else
14897                 if(x == BOARD_RGHT+1) {
14898                     if(y >= gameInfo.holdingsSize) break;
14899                     boards[0][y][BOARD_WIDTH-2] = 0;
14900                 } else break;
14901             }
14902             boards[0][y][x] = EmptySquare;
14903             DrawPosition(FALSE, boards[0]);
14904         }
14905         break;
14906
14907       case PromotePiece:
14908         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14909            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14910             selection = (ChessSquare) (PROMOTED piece);
14911         } else if(piece == EmptySquare) selection = WhiteSilver;
14912         else selection = (ChessSquare)((int)piece - 1);
14913         goto defaultlabel;
14914
14915       case DemotePiece:
14916         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14917            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14918             selection = (ChessSquare) (DEMOTED piece);
14919         } else if(piece == EmptySquare) selection = BlackSilver;
14920         else selection = (ChessSquare)((int)piece + 1);
14921         goto defaultlabel;
14922
14923       case WhiteQueen:
14924       case BlackQueen:
14925         if(gameInfo.variant == VariantShatranj ||
14926            gameInfo.variant == VariantXiangqi  ||
14927            gameInfo.variant == VariantCourier  ||
14928            gameInfo.variant == VariantASEAN    ||
14929            gameInfo.variant == VariantMakruk     )
14930             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14931         goto defaultlabel;
14932
14933       case WhiteKing:
14934       case BlackKing:
14935         if(gameInfo.variant == VariantXiangqi)
14936             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14937         if(gameInfo.variant == VariantKnightmate)
14938             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14939       default:
14940         defaultlabel:
14941         if (gameMode == IcsExamining) {
14942             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14943             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14944                      PieceToChar(selection), AAA + x, ONE + y);
14945             SendToICS(buf);
14946         } else {
14947             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14948                 int n;
14949                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14950                     n = PieceToNumber(selection - BlackPawn);
14951                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14952                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14953                     boards[0][BOARD_HEIGHT-1-n][1]++;
14954                 } else
14955                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14956                     n = PieceToNumber(selection);
14957                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14958                     boards[0][n][BOARD_WIDTH-1] = selection;
14959                     boards[0][n][BOARD_WIDTH-2]++;
14960                 }
14961             } else
14962             boards[0][y][x] = selection;
14963             DrawPosition(TRUE, boards[0]);
14964             ClearHighlights();
14965             fromX = fromY = -1;
14966         }
14967         break;
14968     }
14969 }
14970
14971
14972 void
14973 DropMenuEvent (ChessSquare selection, int x, int y)
14974 {
14975     ChessMove moveType;
14976
14977     switch (gameMode) {
14978       case IcsPlayingWhite:
14979       case MachinePlaysBlack:
14980         if (!WhiteOnMove(currentMove)) {
14981             DisplayMoveError(_("It is Black's turn"));
14982             return;
14983         }
14984         moveType = WhiteDrop;
14985         break;
14986       case IcsPlayingBlack:
14987       case MachinePlaysWhite:
14988         if (WhiteOnMove(currentMove)) {
14989             DisplayMoveError(_("It is White's turn"));
14990             return;
14991         }
14992         moveType = BlackDrop;
14993         break;
14994       case EditGame:
14995         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14996         break;
14997       default:
14998         return;
14999     }
15000
15001     if (moveType == BlackDrop && selection < BlackPawn) {
15002       selection = (ChessSquare) ((int) selection
15003                                  + (int) BlackPawn - (int) WhitePawn);
15004     }
15005     if (boards[currentMove][y][x] != EmptySquare) {
15006         DisplayMoveError(_("That square is occupied"));
15007         return;
15008     }
15009
15010     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15011 }
15012
15013 void
15014 AcceptEvent ()
15015 {
15016     /* Accept a pending offer of any kind from opponent */
15017
15018     if (appData.icsActive) {
15019         SendToICS(ics_prefix);
15020         SendToICS("accept\n");
15021     } else if (cmailMsgLoaded) {
15022         if (currentMove == cmailOldMove &&
15023             commentList[cmailOldMove] != NULL &&
15024             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15025                    "Black offers a draw" : "White offers a draw")) {
15026             TruncateGame();
15027             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15028             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15029         } else {
15030             DisplayError(_("There is no pending offer on this move"), 0);
15031             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15032         }
15033     } else {
15034         /* Not used for offers from chess program */
15035     }
15036 }
15037
15038 void
15039 DeclineEvent ()
15040 {
15041     /* Decline a pending offer of any kind from opponent */
15042
15043     if (appData.icsActive) {
15044         SendToICS(ics_prefix);
15045         SendToICS("decline\n");
15046     } else if (cmailMsgLoaded) {
15047         if (currentMove == cmailOldMove &&
15048             commentList[cmailOldMove] != NULL &&
15049             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15050                    "Black offers a draw" : "White offers a draw")) {
15051 #ifdef NOTDEF
15052             AppendComment(cmailOldMove, "Draw declined", TRUE);
15053             DisplayComment(cmailOldMove - 1, "Draw declined");
15054 #endif /*NOTDEF*/
15055         } else {
15056             DisplayError(_("There is no pending offer on this move"), 0);
15057         }
15058     } else {
15059         /* Not used for offers from chess program */
15060     }
15061 }
15062
15063 void
15064 RematchEvent ()
15065 {
15066     /* Issue ICS rematch command */
15067     if (appData.icsActive) {
15068         SendToICS(ics_prefix);
15069         SendToICS("rematch\n");
15070     }
15071 }
15072
15073 void
15074 CallFlagEvent ()
15075 {
15076     /* Call your opponent's flag (claim a win on time) */
15077     if (appData.icsActive) {
15078         SendToICS(ics_prefix);
15079         SendToICS("flag\n");
15080     } else {
15081         switch (gameMode) {
15082           default:
15083             return;
15084           case MachinePlaysWhite:
15085             if (whiteFlag) {
15086                 if (blackFlag)
15087                   GameEnds(GameIsDrawn, "Both players ran out of time",
15088                            GE_PLAYER);
15089                 else
15090                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15091             } else {
15092                 DisplayError(_("Your opponent is not out of time"), 0);
15093             }
15094             break;
15095           case MachinePlaysBlack:
15096             if (blackFlag) {
15097                 if (whiteFlag)
15098                   GameEnds(GameIsDrawn, "Both players ran out of time",
15099                            GE_PLAYER);
15100                 else
15101                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15102             } else {
15103                 DisplayError(_("Your opponent is not out of time"), 0);
15104             }
15105             break;
15106         }
15107     }
15108 }
15109
15110 void
15111 ClockClick (int which)
15112 {       // [HGM] code moved to back-end from winboard.c
15113         if(which) { // black clock
15114           if (gameMode == EditPosition || gameMode == IcsExamining) {
15115             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15116             SetBlackToPlayEvent();
15117           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15118           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15119           } else if (shiftKey) {
15120             AdjustClock(which, -1);
15121           } else if (gameMode == IcsPlayingWhite ||
15122                      gameMode == MachinePlaysBlack) {
15123             CallFlagEvent();
15124           }
15125         } else { // white clock
15126           if (gameMode == EditPosition || gameMode == IcsExamining) {
15127             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15128             SetWhiteToPlayEvent();
15129           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15130           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15131           } else if (shiftKey) {
15132             AdjustClock(which, -1);
15133           } else if (gameMode == IcsPlayingBlack ||
15134                    gameMode == MachinePlaysWhite) {
15135             CallFlagEvent();
15136           }
15137         }
15138 }
15139
15140 void
15141 DrawEvent ()
15142 {
15143     /* Offer draw or accept pending draw offer from opponent */
15144
15145     if (appData.icsActive) {
15146         /* Note: tournament rules require draw offers to be
15147            made after you make your move but before you punch
15148            your clock.  Currently ICS doesn't let you do that;
15149            instead, you immediately punch your clock after making
15150            a move, but you can offer a draw at any time. */
15151
15152         SendToICS(ics_prefix);
15153         SendToICS("draw\n");
15154         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15155     } else if (cmailMsgLoaded) {
15156         if (currentMove == cmailOldMove &&
15157             commentList[cmailOldMove] != NULL &&
15158             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15159                    "Black offers a draw" : "White offers a draw")) {
15160             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15161             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15162         } else if (currentMove == cmailOldMove + 1) {
15163             char *offer = WhiteOnMove(cmailOldMove) ?
15164               "White offers a draw" : "Black offers a draw";
15165             AppendComment(currentMove, offer, TRUE);
15166             DisplayComment(currentMove - 1, offer);
15167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15168         } else {
15169             DisplayError(_("You must make your move before offering a draw"), 0);
15170             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15171         }
15172     } else if (first.offeredDraw) {
15173         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15174     } else {
15175         if (first.sendDrawOffers) {
15176             SendToProgram("draw\n", &first);
15177             userOfferedDraw = TRUE;
15178         }
15179     }
15180 }
15181
15182 void
15183 AdjournEvent ()
15184 {
15185     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15186
15187     if (appData.icsActive) {
15188         SendToICS(ics_prefix);
15189         SendToICS("adjourn\n");
15190     } else {
15191         /* Currently GNU Chess doesn't offer or accept Adjourns */
15192     }
15193 }
15194
15195
15196 void
15197 AbortEvent ()
15198 {
15199     /* Offer Abort or accept pending Abort offer from opponent */
15200
15201     if (appData.icsActive) {
15202         SendToICS(ics_prefix);
15203         SendToICS("abort\n");
15204     } else {
15205         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15206     }
15207 }
15208
15209 void
15210 ResignEvent ()
15211 {
15212     /* Resign.  You can do this even if it's not your turn. */
15213
15214     if (appData.icsActive) {
15215         SendToICS(ics_prefix);
15216         SendToICS("resign\n");
15217     } else {
15218         switch (gameMode) {
15219           case MachinePlaysWhite:
15220             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15221             break;
15222           case MachinePlaysBlack:
15223             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15224             break;
15225           case EditGame:
15226             if (cmailMsgLoaded) {
15227                 TruncateGame();
15228                 if (WhiteOnMove(cmailOldMove)) {
15229                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15230                 } else {
15231                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15232                 }
15233                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15234             }
15235             break;
15236           default:
15237             break;
15238         }
15239     }
15240 }
15241
15242
15243 void
15244 StopObservingEvent ()
15245 {
15246     /* Stop observing current games */
15247     SendToICS(ics_prefix);
15248     SendToICS("unobserve\n");
15249 }
15250
15251 void
15252 StopExaminingEvent ()
15253 {
15254     /* Stop observing current game */
15255     SendToICS(ics_prefix);
15256     SendToICS("unexamine\n");
15257 }
15258
15259 void
15260 ForwardInner (int target)
15261 {
15262     int limit; int oldSeekGraphUp = seekGraphUp;
15263
15264     if (appData.debugMode)
15265         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15266                 target, currentMove, forwardMostMove);
15267
15268     if (gameMode == EditPosition)
15269       return;
15270
15271     seekGraphUp = FALSE;
15272     MarkTargetSquares(1);
15273
15274     if (gameMode == PlayFromGameFile && !pausing)
15275       PauseEvent();
15276
15277     if (gameMode == IcsExamining && pausing)
15278       limit = pauseExamForwardMostMove;
15279     else
15280       limit = forwardMostMove;
15281
15282     if (target > limit) target = limit;
15283
15284     if (target > 0 && moveList[target - 1][0]) {
15285         int fromX, fromY, toX, toY;
15286         toX = moveList[target - 1][2] - AAA;
15287         toY = moveList[target - 1][3] - ONE;
15288         if (moveList[target - 1][1] == '@') {
15289             if (appData.highlightLastMove) {
15290                 SetHighlights(-1, -1, toX, toY);
15291             }
15292         } else {
15293             fromX = moveList[target - 1][0] - AAA;
15294             fromY = moveList[target - 1][1] - ONE;
15295             if (target == currentMove + 1) {
15296                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15297             }
15298             if (appData.highlightLastMove) {
15299                 SetHighlights(fromX, fromY, toX, toY);
15300             }
15301         }
15302     }
15303     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15304         gameMode == Training || gameMode == PlayFromGameFile ||
15305         gameMode == AnalyzeFile) {
15306         while (currentMove < target) {
15307             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15308             SendMoveToProgram(currentMove++, &first);
15309         }
15310     } else {
15311         currentMove = target;
15312     }
15313
15314     if (gameMode == EditGame || gameMode == EndOfGame) {
15315         whiteTimeRemaining = timeRemaining[0][currentMove];
15316         blackTimeRemaining = timeRemaining[1][currentMove];
15317     }
15318     DisplayBothClocks();
15319     DisplayMove(currentMove - 1);
15320     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15321     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15322     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15323         DisplayComment(currentMove - 1, commentList[currentMove]);
15324     }
15325     ClearMap(); // [HGM] exclude: invalidate map
15326 }
15327
15328
15329 void
15330 ForwardEvent ()
15331 {
15332     if (gameMode == IcsExamining && !pausing) {
15333         SendToICS(ics_prefix);
15334         SendToICS("forward\n");
15335     } else {
15336         ForwardInner(currentMove + 1);
15337     }
15338 }
15339
15340 void
15341 ToEndEvent ()
15342 {
15343     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15344         /* to optimze, we temporarily turn off analysis mode while we feed
15345          * the remaining moves to the engine. Otherwise we get analysis output
15346          * after each move.
15347          */
15348         if (first.analysisSupport) {
15349           SendToProgram("exit\nforce\n", &first);
15350           first.analyzing = FALSE;
15351         }
15352     }
15353
15354     if (gameMode == IcsExamining && !pausing) {
15355         SendToICS(ics_prefix);
15356         SendToICS("forward 999999\n");
15357     } else {
15358         ForwardInner(forwardMostMove);
15359     }
15360
15361     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15362         /* we have fed all the moves, so reactivate analysis mode */
15363         SendToProgram("analyze\n", &first);
15364         first.analyzing = TRUE;
15365         /*first.maybeThinking = TRUE;*/
15366         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15367     }
15368 }
15369
15370 void
15371 BackwardInner (int target)
15372 {
15373     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15374
15375     if (appData.debugMode)
15376         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15377                 target, currentMove, forwardMostMove);
15378
15379     if (gameMode == EditPosition) return;
15380     seekGraphUp = FALSE;
15381     MarkTargetSquares(1);
15382     if (currentMove <= backwardMostMove) {
15383         ClearHighlights();
15384         DrawPosition(full_redraw, boards[currentMove]);
15385         return;
15386     }
15387     if (gameMode == PlayFromGameFile && !pausing)
15388       PauseEvent();
15389
15390     if (moveList[target][0]) {
15391         int fromX, fromY, toX, toY;
15392         toX = moveList[target][2] - AAA;
15393         toY = moveList[target][3] - ONE;
15394         if (moveList[target][1] == '@') {
15395             if (appData.highlightLastMove) {
15396                 SetHighlights(-1, -1, toX, toY);
15397             }
15398         } else {
15399             fromX = moveList[target][0] - AAA;
15400             fromY = moveList[target][1] - ONE;
15401             if (target == currentMove - 1) {
15402                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15403             }
15404             if (appData.highlightLastMove) {
15405                 SetHighlights(fromX, fromY, toX, toY);
15406             }
15407         }
15408     }
15409     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15410         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15411         while (currentMove > target) {
15412             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15413                 // null move cannot be undone. Reload program with move history before it.
15414                 int i;
15415                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15416                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15417                 }
15418                 SendBoard(&first, i);
15419               if(second.analyzing) SendBoard(&second, i);
15420                 for(currentMove=i; currentMove<target; currentMove++) {
15421                     SendMoveToProgram(currentMove, &first);
15422                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15423                 }
15424                 break;
15425             }
15426             SendToBoth("undo\n");
15427             currentMove--;
15428         }
15429     } else {
15430         currentMove = target;
15431     }
15432
15433     if (gameMode == EditGame || gameMode == EndOfGame) {
15434         whiteTimeRemaining = timeRemaining[0][currentMove];
15435         blackTimeRemaining = timeRemaining[1][currentMove];
15436     }
15437     DisplayBothClocks();
15438     DisplayMove(currentMove - 1);
15439     DrawPosition(full_redraw, boards[currentMove]);
15440     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15441     // [HGM] PV info: routine tests if comment empty
15442     DisplayComment(currentMove - 1, commentList[currentMove]);
15443     ClearMap(); // [HGM] exclude: invalidate map
15444 }
15445
15446 void
15447 BackwardEvent ()
15448 {
15449     if (gameMode == IcsExamining && !pausing) {
15450         SendToICS(ics_prefix);
15451         SendToICS("backward\n");
15452     } else {
15453         BackwardInner(currentMove - 1);
15454     }
15455 }
15456
15457 void
15458 ToStartEvent ()
15459 {
15460     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15461         /* to optimize, we temporarily turn off analysis mode while we undo
15462          * all the moves. Otherwise we get analysis output after each undo.
15463          */
15464         if (first.analysisSupport) {
15465           SendToProgram("exit\nforce\n", &first);
15466           first.analyzing = FALSE;
15467         }
15468     }
15469
15470     if (gameMode == IcsExamining && !pausing) {
15471         SendToICS(ics_prefix);
15472         SendToICS("backward 999999\n");
15473     } else {
15474         BackwardInner(backwardMostMove);
15475     }
15476
15477     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15478         /* we have fed all the moves, so reactivate analysis mode */
15479         SendToProgram("analyze\n", &first);
15480         first.analyzing = TRUE;
15481         /*first.maybeThinking = TRUE;*/
15482         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15483     }
15484 }
15485
15486 void
15487 ToNrEvent (int to)
15488 {
15489   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15490   if (to >= forwardMostMove) to = forwardMostMove;
15491   if (to <= backwardMostMove) to = backwardMostMove;
15492   if (to < currentMove) {
15493     BackwardInner(to);
15494   } else {
15495     ForwardInner(to);
15496   }
15497 }
15498
15499 void
15500 RevertEvent (Boolean annotate)
15501 {
15502     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15503         return;
15504     }
15505     if (gameMode != IcsExamining) {
15506         DisplayError(_("You are not examining a game"), 0);
15507         return;
15508     }
15509     if (pausing) {
15510         DisplayError(_("You can't revert while pausing"), 0);
15511         return;
15512     }
15513     SendToICS(ics_prefix);
15514     SendToICS("revert\n");
15515 }
15516
15517 void
15518 RetractMoveEvent ()
15519 {
15520     switch (gameMode) {
15521       case MachinePlaysWhite:
15522       case MachinePlaysBlack:
15523         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15524             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15525             return;
15526         }
15527         if (forwardMostMove < 2) return;
15528         currentMove = forwardMostMove = forwardMostMove - 2;
15529         whiteTimeRemaining = timeRemaining[0][currentMove];
15530         blackTimeRemaining = timeRemaining[1][currentMove];
15531         DisplayBothClocks();
15532         DisplayMove(currentMove - 1);
15533         ClearHighlights();/*!! could figure this out*/
15534         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15535         SendToProgram("remove\n", &first);
15536         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15537         break;
15538
15539       case BeginningOfGame:
15540       default:
15541         break;
15542
15543       case IcsPlayingWhite:
15544       case IcsPlayingBlack:
15545         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15546             SendToICS(ics_prefix);
15547             SendToICS("takeback 2\n");
15548         } else {
15549             SendToICS(ics_prefix);
15550             SendToICS("takeback 1\n");
15551         }
15552         break;
15553     }
15554 }
15555
15556 void
15557 MoveNowEvent ()
15558 {
15559     ChessProgramState *cps;
15560
15561     switch (gameMode) {
15562       case MachinePlaysWhite:
15563         if (!WhiteOnMove(forwardMostMove)) {
15564             DisplayError(_("It is your turn"), 0);
15565             return;
15566         }
15567         cps = &first;
15568         break;
15569       case MachinePlaysBlack:
15570         if (WhiteOnMove(forwardMostMove)) {
15571             DisplayError(_("It is your turn"), 0);
15572             return;
15573         }
15574         cps = &first;
15575         break;
15576       case TwoMachinesPlay:
15577         if (WhiteOnMove(forwardMostMove) ==
15578             (first.twoMachinesColor[0] == 'w')) {
15579             cps = &first;
15580         } else {
15581             cps = &second;
15582         }
15583         break;
15584       case BeginningOfGame:
15585       default:
15586         return;
15587     }
15588     SendToProgram("?\n", cps);
15589 }
15590
15591 void
15592 TruncateGameEvent ()
15593 {
15594     EditGameEvent();
15595     if (gameMode != EditGame) return;
15596     TruncateGame();
15597 }
15598
15599 void
15600 TruncateGame ()
15601 {
15602     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15603     if (forwardMostMove > currentMove) {
15604         if (gameInfo.resultDetails != NULL) {
15605             free(gameInfo.resultDetails);
15606             gameInfo.resultDetails = NULL;
15607             gameInfo.result = GameUnfinished;
15608         }
15609         forwardMostMove = currentMove;
15610         HistorySet(parseList, backwardMostMove, forwardMostMove,
15611                    currentMove-1);
15612     }
15613 }
15614
15615 void
15616 HintEvent ()
15617 {
15618     if (appData.noChessProgram) return;
15619     switch (gameMode) {
15620       case MachinePlaysWhite:
15621         if (WhiteOnMove(forwardMostMove)) {
15622             DisplayError(_("Wait until your turn."), 0);
15623             return;
15624         }
15625         break;
15626       case BeginningOfGame:
15627       case MachinePlaysBlack:
15628         if (!WhiteOnMove(forwardMostMove)) {
15629             DisplayError(_("Wait until your turn."), 0);
15630             return;
15631         }
15632         break;
15633       default:
15634         DisplayError(_("No hint available"), 0);
15635         return;
15636     }
15637     SendToProgram("hint\n", &first);
15638     hintRequested = TRUE;
15639 }
15640
15641 void
15642 CreateBookEvent ()
15643 {
15644     ListGame * lg = (ListGame *) gameList.head;
15645     FILE *f, *g;
15646     int nItem;
15647     static int secondTime = FALSE;
15648
15649     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15650         DisplayError(_("Game list not loaded or empty"), 0);
15651         return;
15652     }
15653
15654     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15655         fclose(g);
15656         secondTime++;
15657         DisplayNote(_("Book file exists! Try again for overwrite."));
15658         return;
15659     }
15660
15661     creatingBook = TRUE;
15662     secondTime = FALSE;
15663
15664     /* Get list size */
15665     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15666         LoadGame(f, nItem, "", TRUE);
15667         AddGameToBook(TRUE);
15668         lg = (ListGame *) lg->node.succ;
15669     }
15670
15671     creatingBook = FALSE;
15672     FlushBook();
15673 }
15674
15675 void
15676 BookEvent ()
15677 {
15678     if (appData.noChessProgram) return;
15679     switch (gameMode) {
15680       case MachinePlaysWhite:
15681         if (WhiteOnMove(forwardMostMove)) {
15682             DisplayError(_("Wait until your turn."), 0);
15683             return;
15684         }
15685         break;
15686       case BeginningOfGame:
15687       case MachinePlaysBlack:
15688         if (!WhiteOnMove(forwardMostMove)) {
15689             DisplayError(_("Wait until your turn."), 0);
15690             return;
15691         }
15692         break;
15693       case EditPosition:
15694         EditPositionDone(TRUE);
15695         break;
15696       case TwoMachinesPlay:
15697         return;
15698       default:
15699         break;
15700     }
15701     SendToProgram("bk\n", &first);
15702     bookOutput[0] = NULLCHAR;
15703     bookRequested = TRUE;
15704 }
15705
15706 void
15707 AboutGameEvent ()
15708 {
15709     char *tags = PGNTags(&gameInfo);
15710     TagsPopUp(tags, CmailMsg());
15711     free(tags);
15712 }
15713
15714 /* end button procedures */
15715
15716 void
15717 PrintPosition (FILE *fp, int move)
15718 {
15719     int i, j;
15720
15721     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15722         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15723             char c = PieceToChar(boards[move][i][j]);
15724             fputc(c == 'x' ? '.' : c, fp);
15725             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15726         }
15727     }
15728     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15729       fprintf(fp, "white to play\n");
15730     else
15731       fprintf(fp, "black to play\n");
15732 }
15733
15734 void
15735 PrintOpponents (FILE *fp)
15736 {
15737     if (gameInfo.white != NULL) {
15738         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15739     } else {
15740         fprintf(fp, "\n");
15741     }
15742 }
15743
15744 /* Find last component of program's own name, using some heuristics */
15745 void
15746 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15747 {
15748     char *p, *q, c;
15749     int local = (strcmp(host, "localhost") == 0);
15750     while (!local && (p = strchr(prog, ';')) != NULL) {
15751         p++;
15752         while (*p == ' ') p++;
15753         prog = p;
15754     }
15755     if (*prog == '"' || *prog == '\'') {
15756         q = strchr(prog + 1, *prog);
15757     } else {
15758         q = strchr(prog, ' ');
15759     }
15760     if (q == NULL) q = prog + strlen(prog);
15761     p = q;
15762     while (p >= prog && *p != '/' && *p != '\\') p--;
15763     p++;
15764     if(p == prog && *p == '"') p++;
15765     c = *q; *q = 0;
15766     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15767     memcpy(buf, p, q - p);
15768     buf[q - p] = NULLCHAR;
15769     if (!local) {
15770         strcat(buf, "@");
15771         strcat(buf, host);
15772     }
15773 }
15774
15775 char *
15776 TimeControlTagValue ()
15777 {
15778     char buf[MSG_SIZ];
15779     if (!appData.clockMode) {
15780       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15781     } else if (movesPerSession > 0) {
15782       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15783     } else if (timeIncrement == 0) {
15784       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15785     } else {
15786       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15787     }
15788     return StrSave(buf);
15789 }
15790
15791 void
15792 SetGameInfo ()
15793 {
15794     /* This routine is used only for certain modes */
15795     VariantClass v = gameInfo.variant;
15796     ChessMove r = GameUnfinished;
15797     char *p = NULL;
15798
15799     if(keepInfo) return;
15800
15801     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15802         r = gameInfo.result;
15803         p = gameInfo.resultDetails;
15804         gameInfo.resultDetails = NULL;
15805     }
15806     ClearGameInfo(&gameInfo);
15807     gameInfo.variant = v;
15808
15809     switch (gameMode) {
15810       case MachinePlaysWhite:
15811         gameInfo.event = StrSave( appData.pgnEventHeader );
15812         gameInfo.site = StrSave(HostName());
15813         gameInfo.date = PGNDate();
15814         gameInfo.round = StrSave("-");
15815         gameInfo.white = StrSave(first.tidy);
15816         gameInfo.black = StrSave(UserName());
15817         gameInfo.timeControl = TimeControlTagValue();
15818         break;
15819
15820       case MachinePlaysBlack:
15821         gameInfo.event = StrSave( appData.pgnEventHeader );
15822         gameInfo.site = StrSave(HostName());
15823         gameInfo.date = PGNDate();
15824         gameInfo.round = StrSave("-");
15825         gameInfo.white = StrSave(UserName());
15826         gameInfo.black = StrSave(first.tidy);
15827         gameInfo.timeControl = TimeControlTagValue();
15828         break;
15829
15830       case TwoMachinesPlay:
15831         gameInfo.event = StrSave( appData.pgnEventHeader );
15832         gameInfo.site = StrSave(HostName());
15833         gameInfo.date = PGNDate();
15834         if (roundNr > 0) {
15835             char buf[MSG_SIZ];
15836             snprintf(buf, MSG_SIZ, "%d", roundNr);
15837             gameInfo.round = StrSave(buf);
15838         } else {
15839             gameInfo.round = StrSave("-");
15840         }
15841         if (first.twoMachinesColor[0] == 'w') {
15842             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15843             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15844         } else {
15845             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15846             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15847         }
15848         gameInfo.timeControl = TimeControlTagValue();
15849         break;
15850
15851       case EditGame:
15852         gameInfo.event = StrSave("Edited game");
15853         gameInfo.site = StrSave(HostName());
15854         gameInfo.date = PGNDate();
15855         gameInfo.round = StrSave("-");
15856         gameInfo.white = StrSave("-");
15857         gameInfo.black = StrSave("-");
15858         gameInfo.result = r;
15859         gameInfo.resultDetails = p;
15860         break;
15861
15862       case EditPosition:
15863         gameInfo.event = StrSave("Edited position");
15864         gameInfo.site = StrSave(HostName());
15865         gameInfo.date = PGNDate();
15866         gameInfo.round = StrSave("-");
15867         gameInfo.white = StrSave("-");
15868         gameInfo.black = StrSave("-");
15869         break;
15870
15871       case IcsPlayingWhite:
15872       case IcsPlayingBlack:
15873       case IcsObserving:
15874       case IcsExamining:
15875         break;
15876
15877       case PlayFromGameFile:
15878         gameInfo.event = StrSave("Game from non-PGN file");
15879         gameInfo.site = StrSave(HostName());
15880         gameInfo.date = PGNDate();
15881         gameInfo.round = StrSave("-");
15882         gameInfo.white = StrSave("?");
15883         gameInfo.black = StrSave("?");
15884         break;
15885
15886       default:
15887         break;
15888     }
15889 }
15890
15891 void
15892 ReplaceComment (int index, char *text)
15893 {
15894     int len;
15895     char *p;
15896     float score;
15897
15898     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15899        pvInfoList[index-1].depth == len &&
15900        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15901        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15902     while (*text == '\n') text++;
15903     len = strlen(text);
15904     while (len > 0 && text[len - 1] == '\n') len--;
15905
15906     if (commentList[index] != NULL)
15907       free(commentList[index]);
15908
15909     if (len == 0) {
15910         commentList[index] = NULL;
15911         return;
15912     }
15913   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15914       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15915       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15916     commentList[index] = (char *) malloc(len + 2);
15917     strncpy(commentList[index], text, len);
15918     commentList[index][len] = '\n';
15919     commentList[index][len + 1] = NULLCHAR;
15920   } else {
15921     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15922     char *p;
15923     commentList[index] = (char *) malloc(len + 7);
15924     safeStrCpy(commentList[index], "{\n", 3);
15925     safeStrCpy(commentList[index]+2, text, len+1);
15926     commentList[index][len+2] = NULLCHAR;
15927     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15928     strcat(commentList[index], "\n}\n");
15929   }
15930 }
15931
15932 void
15933 CrushCRs (char *text)
15934 {
15935   char *p = text;
15936   char *q = text;
15937   char ch;
15938
15939   do {
15940     ch = *p++;
15941     if (ch == '\r') continue;
15942     *q++ = ch;
15943   } while (ch != '\0');
15944 }
15945
15946 void
15947 AppendComment (int index, char *text, Boolean addBraces)
15948 /* addBraces  tells if we should add {} */
15949 {
15950     int oldlen, len;
15951     char *old;
15952
15953 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15954     if(addBraces == 3) addBraces = 0; else // force appending literally
15955     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15956
15957     CrushCRs(text);
15958     while (*text == '\n') text++;
15959     len = strlen(text);
15960     while (len > 0 && text[len - 1] == '\n') len--;
15961     text[len] = NULLCHAR;
15962
15963     if (len == 0) return;
15964
15965     if (commentList[index] != NULL) {
15966       Boolean addClosingBrace = addBraces;
15967         old = commentList[index];
15968         oldlen = strlen(old);
15969         while(commentList[index][oldlen-1] ==  '\n')
15970           commentList[index][--oldlen] = NULLCHAR;
15971         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15972         safeStrCpy(commentList[index], old, oldlen + len + 6);
15973         free(old);
15974         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15975         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15976           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15977           while (*text == '\n') { text++; len--; }
15978           commentList[index][--oldlen] = NULLCHAR;
15979       }
15980         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15981         else          strcat(commentList[index], "\n");
15982         strcat(commentList[index], text);
15983         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15984         else          strcat(commentList[index], "\n");
15985     } else {
15986         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15987         if(addBraces)
15988           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15989         else commentList[index][0] = NULLCHAR;
15990         strcat(commentList[index], text);
15991         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15992         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15993     }
15994 }
15995
15996 static char *
15997 FindStr (char * text, char * sub_text)
15998 {
15999     char * result = strstr( text, sub_text );
16000
16001     if( result != NULL ) {
16002         result += strlen( sub_text );
16003     }
16004
16005     return result;
16006 }
16007
16008 /* [AS] Try to extract PV info from PGN comment */
16009 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16010 char *
16011 GetInfoFromComment (int index, char * text)
16012 {
16013     char * sep = text, *p;
16014
16015     if( text != NULL && index > 0 ) {
16016         int score = 0;
16017         int depth = 0;
16018         int time = -1, sec = 0, deci;
16019         char * s_eval = FindStr( text, "[%eval " );
16020         char * s_emt = FindStr( text, "[%emt " );
16021 #if 0
16022         if( s_eval != NULL || s_emt != NULL ) {
16023 #else
16024         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16025 #endif
16026             /* New style */
16027             char delim;
16028
16029             if( s_eval != NULL ) {
16030                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16031                     return text;
16032                 }
16033
16034                 if( delim != ']' ) {
16035                     return text;
16036                 }
16037             }
16038
16039             if( s_emt != NULL ) {
16040             }
16041                 return text;
16042         }
16043         else {
16044             /* We expect something like: [+|-]nnn.nn/dd */
16045             int score_lo = 0;
16046
16047             if(*text != '{') return text; // [HGM] braces: must be normal comment
16048
16049             sep = strchr( text, '/' );
16050             if( sep == NULL || sep < (text+4) ) {
16051                 return text;
16052             }
16053
16054             p = text;
16055             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16056             if(p[1] == '(') { // comment starts with PV
16057                p = strchr(p, ')'); // locate end of PV
16058                if(p == NULL || sep < p+5) return text;
16059                // at this point we have something like "{(.*) +0.23/6 ..."
16060                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16061                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16062                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16063             }
16064             time = -1; sec = -1; deci = -1;
16065             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16066                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16067                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16068                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16069                 return text;
16070             }
16071
16072             if( score_lo < 0 || score_lo >= 100 ) {
16073                 return text;
16074             }
16075
16076             if(sec >= 0) time = 600*time + 10*sec; else
16077             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16078
16079             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16080
16081             /* [HGM] PV time: now locate end of PV info */
16082             while( *++sep >= '0' && *sep <= '9'); // strip depth
16083             if(time >= 0)
16084             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16085             if(sec >= 0)
16086             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16087             if(deci >= 0)
16088             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16089             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16090         }
16091
16092         if( depth <= 0 ) {
16093             return text;
16094         }
16095
16096         if( time < 0 ) {
16097             time = -1;
16098         }
16099
16100         pvInfoList[index-1].depth = depth;
16101         pvInfoList[index-1].score = score;
16102         pvInfoList[index-1].time  = 10*time; // centi-sec
16103         if(*sep == '}') *sep = 0; else *--sep = '{';
16104         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16105     }
16106     return sep;
16107 }
16108
16109 void
16110 SendToProgram (char *message, ChessProgramState *cps)
16111 {
16112     int count, outCount, error;
16113     char buf[MSG_SIZ];
16114
16115     if (cps->pr == NoProc) return;
16116     Attention(cps);
16117
16118     if (appData.debugMode) {
16119         TimeMark now;
16120         GetTimeMark(&now);
16121         fprintf(debugFP, "%ld >%-6s: %s",
16122                 SubtractTimeMarks(&now, &programStartTime),
16123                 cps->which, message);
16124         if(serverFP)
16125             fprintf(serverFP, "%ld >%-6s: %s",
16126                 SubtractTimeMarks(&now, &programStartTime),
16127                 cps->which, message), fflush(serverFP);
16128     }
16129
16130     count = strlen(message);
16131     outCount = OutputToProcess(cps->pr, message, count, &error);
16132     if (outCount < count && !exiting
16133                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16134       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16135       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16136         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16137             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16138                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16139                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16140                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16141             } else {
16142                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16143                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16144                 gameInfo.result = res;
16145             }
16146             gameInfo.resultDetails = StrSave(buf);
16147         }
16148         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16149         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16150     }
16151 }
16152
16153 void
16154 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16155 {
16156     char *end_str;
16157     char buf[MSG_SIZ];
16158     ChessProgramState *cps = (ChessProgramState *)closure;
16159
16160     if (isr != cps->isr) return; /* Killed intentionally */
16161     if (count <= 0) {
16162         if (count == 0) {
16163             RemoveInputSource(cps->isr);
16164             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16165                     _(cps->which), cps->program);
16166             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16167             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16168                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16169                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16170                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16171                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16172                 } else {
16173                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16174                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16175                     gameInfo.result = res;
16176                 }
16177                 gameInfo.resultDetails = StrSave(buf);
16178             }
16179             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16180             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16181         } else {
16182             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16183                     _(cps->which), cps->program);
16184             RemoveInputSource(cps->isr);
16185
16186             /* [AS] Program is misbehaving badly... kill it */
16187             if( count == -2 ) {
16188                 DestroyChildProcess( cps->pr, 9 );
16189                 cps->pr = NoProc;
16190             }
16191
16192             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16193         }
16194         return;
16195     }
16196
16197     if ((end_str = strchr(message, '\r')) != NULL)
16198       *end_str = NULLCHAR;
16199     if ((end_str = strchr(message, '\n')) != NULL)
16200       *end_str = NULLCHAR;
16201
16202     if (appData.debugMode) {
16203         TimeMark now; int print = 1;
16204         char *quote = ""; char c; int i;
16205
16206         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16207                 char start = message[0];
16208                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16209                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16210                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16211                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16212                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16213                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16214                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16215                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16216                    sscanf(message, "hint: %c", &c)!=1 &&
16217                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16218                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16219                     print = (appData.engineComments >= 2);
16220                 }
16221                 message[0] = start; // restore original message
16222         }
16223         if(print) {
16224                 GetTimeMark(&now);
16225                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16226                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16227                         quote,
16228                         message);
16229                 if(serverFP)
16230                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16231                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16232                         quote,
16233                         message), fflush(serverFP);
16234         }
16235     }
16236
16237     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16238     if (appData.icsEngineAnalyze) {
16239         if (strstr(message, "whisper") != NULL ||
16240              strstr(message, "kibitz") != NULL ||
16241             strstr(message, "tellics") != NULL) return;
16242     }
16243
16244     HandleMachineMove(message, cps);
16245 }
16246
16247
16248 void
16249 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16250 {
16251     char buf[MSG_SIZ];
16252     int seconds;
16253
16254     if( timeControl_2 > 0 ) {
16255         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16256             tc = timeControl_2;
16257         }
16258     }
16259     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16260     inc /= cps->timeOdds;
16261     st  /= cps->timeOdds;
16262
16263     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16264
16265     if (st > 0) {
16266       /* Set exact time per move, normally using st command */
16267       if (cps->stKludge) {
16268         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16269         seconds = st % 60;
16270         if (seconds == 0) {
16271           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16272         } else {
16273           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16274         }
16275       } else {
16276         snprintf(buf, MSG_SIZ, "st %d\n", st);
16277       }
16278     } else {
16279       /* Set conventional or incremental time control, using level command */
16280       if (seconds == 0) {
16281         /* Note old gnuchess bug -- minutes:seconds used to not work.
16282            Fixed in later versions, but still avoid :seconds
16283            when seconds is 0. */
16284         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16285       } else {
16286         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16287                  seconds, inc/1000.);
16288       }
16289     }
16290     SendToProgram(buf, cps);
16291
16292     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16293     /* Orthogonally, limit search to given depth */
16294     if (sd > 0) {
16295       if (cps->sdKludge) {
16296         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16297       } else {
16298         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16299       }
16300       SendToProgram(buf, cps);
16301     }
16302
16303     if(cps->nps >= 0) { /* [HGM] nps */
16304         if(cps->supportsNPS == FALSE)
16305           cps->nps = -1; // don't use if engine explicitly says not supported!
16306         else {
16307           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16308           SendToProgram(buf, cps);
16309         }
16310     }
16311 }
16312
16313 ChessProgramState *
16314 WhitePlayer ()
16315 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16316 {
16317     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16318        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16319         return &second;
16320     return &first;
16321 }
16322
16323 void
16324 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16325 {
16326     char message[MSG_SIZ];
16327     long time, otime;
16328
16329     /* Note: this routine must be called when the clocks are stopped
16330        or when they have *just* been set or switched; otherwise
16331        it will be off by the time since the current tick started.
16332     */
16333     if (machineWhite) {
16334         time = whiteTimeRemaining / 10;
16335         otime = blackTimeRemaining / 10;
16336     } else {
16337         time = blackTimeRemaining / 10;
16338         otime = whiteTimeRemaining / 10;
16339     }
16340     /* [HGM] translate opponent's time by time-odds factor */
16341     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16342
16343     if (time <= 0) time = 1;
16344     if (otime <= 0) otime = 1;
16345
16346     snprintf(message, MSG_SIZ, "time %ld\n", time);
16347     SendToProgram(message, cps);
16348
16349     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16350     SendToProgram(message, cps);
16351 }
16352
16353 char *
16354 EngineDefinedVariant (ChessProgramState *cps, int n)
16355 {   // return name of n-th unknown variant that engine supports
16356     static char buf[MSG_SIZ];
16357     char *p, *s = cps->variants;
16358     if(!s) return NULL;
16359     do { // parse string from variants feature
16360       VariantClass v;
16361         p = strchr(s, ',');
16362         if(p) *p = NULLCHAR;
16363       v = StringToVariant(s);
16364       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16365         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16366             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16367         }
16368         if(p) *p++ = ',';
16369         if(n < 0) return buf;
16370     } while(s = p);
16371     return NULL;
16372 }
16373
16374 int
16375 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16376 {
16377   char buf[MSG_SIZ];
16378   int len = strlen(name);
16379   int val;
16380
16381   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16382     (*p) += len + 1;
16383     sscanf(*p, "%d", &val);
16384     *loc = (val != 0);
16385     while (**p && **p != ' ')
16386       (*p)++;
16387     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16388     SendToProgram(buf, cps);
16389     return TRUE;
16390   }
16391   return FALSE;
16392 }
16393
16394 int
16395 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16396 {
16397   char buf[MSG_SIZ];
16398   int len = strlen(name);
16399   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16400     (*p) += len + 1;
16401     sscanf(*p, "%d", loc);
16402     while (**p && **p != ' ') (*p)++;
16403     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16404     SendToProgram(buf, cps);
16405     return TRUE;
16406   }
16407   return FALSE;
16408 }
16409
16410 int
16411 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16412 {
16413   char buf[MSG_SIZ];
16414   int len = strlen(name);
16415   if (strncmp((*p), name, len) == 0
16416       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16417     (*p) += len + 2;
16418     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16419     sscanf(*p, "%[^\"]", *loc);
16420     while (**p && **p != '\"') (*p)++;
16421     if (**p == '\"') (*p)++;
16422     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16423     SendToProgram(buf, cps);
16424     return TRUE;
16425   }
16426   return FALSE;
16427 }
16428
16429 int
16430 ParseOption (Option *opt, ChessProgramState *cps)
16431 // [HGM] options: process the string that defines an engine option, and determine
16432 // name, type, default value, and allowed value range
16433 {
16434         char *p, *q, buf[MSG_SIZ];
16435         int n, min = (-1)<<31, max = 1<<31, def;
16436
16437         if(p = strstr(opt->name, " -spin ")) {
16438             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16439             if(max < min) max = min; // enforce consistency
16440             if(def < min) def = min;
16441             if(def > max) def = max;
16442             opt->value = def;
16443             opt->min = min;
16444             opt->max = max;
16445             opt->type = Spin;
16446         } else if((p = strstr(opt->name, " -slider "))) {
16447             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16448             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16449             if(max < min) max = min; // enforce consistency
16450             if(def < min) def = min;
16451             if(def > max) def = max;
16452             opt->value = def;
16453             opt->min = min;
16454             opt->max = max;
16455             opt->type = Spin; // Slider;
16456         } else if((p = strstr(opt->name, " -string "))) {
16457             opt->textValue = p+9;
16458             opt->type = TextBox;
16459         } else if((p = strstr(opt->name, " -file "))) {
16460             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16461             opt->textValue = p+7;
16462             opt->type = FileName; // FileName;
16463         } else if((p = strstr(opt->name, " -path "))) {
16464             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16465             opt->textValue = p+7;
16466             opt->type = PathName; // PathName;
16467         } else if(p = strstr(opt->name, " -check ")) {
16468             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16469             opt->value = (def != 0);
16470             opt->type = CheckBox;
16471         } else if(p = strstr(opt->name, " -combo ")) {
16472             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16473             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16474             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16475             opt->value = n = 0;
16476             while(q = StrStr(q, " /// ")) {
16477                 n++; *q = 0;    // count choices, and null-terminate each of them
16478                 q += 5;
16479                 if(*q == '*') { // remember default, which is marked with * prefix
16480                     q++;
16481                     opt->value = n;
16482                 }
16483                 cps->comboList[cps->comboCnt++] = q;
16484             }
16485             cps->comboList[cps->comboCnt++] = NULL;
16486             opt->max = n + 1;
16487             opt->type = ComboBox;
16488         } else if(p = strstr(opt->name, " -button")) {
16489             opt->type = Button;
16490         } else if(p = strstr(opt->name, " -save")) {
16491             opt->type = SaveButton;
16492         } else return FALSE;
16493         *p = 0; // terminate option name
16494         // now look if the command-line options define a setting for this engine option.
16495         if(cps->optionSettings && cps->optionSettings[0])
16496             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16497         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16498           snprintf(buf, MSG_SIZ, "option %s", p);
16499                 if(p = strstr(buf, ",")) *p = 0;
16500                 if(q = strchr(buf, '=')) switch(opt->type) {
16501                     case ComboBox:
16502                         for(n=0; n<opt->max; n++)
16503                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16504                         break;
16505                     case TextBox:
16506                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16507                         break;
16508                     case Spin:
16509                     case CheckBox:
16510                         opt->value = atoi(q+1);
16511                     default:
16512                         break;
16513                 }
16514                 strcat(buf, "\n");
16515                 SendToProgram(buf, cps);
16516         }
16517         return TRUE;
16518 }
16519
16520 void
16521 FeatureDone (ChessProgramState *cps, int val)
16522 {
16523   DelayedEventCallback cb = GetDelayedEvent();
16524   if ((cb == InitBackEnd3 && cps == &first) ||
16525       (cb == SettingsMenuIfReady && cps == &second) ||
16526       (cb == LoadEngine) ||
16527       (cb == TwoMachinesEventIfReady)) {
16528     CancelDelayedEvent();
16529     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16530   }
16531   cps->initDone = val;
16532   if(val) cps->reload = FALSE;
16533 }
16534
16535 /* Parse feature command from engine */
16536 void
16537 ParseFeatures (char *args, ChessProgramState *cps)
16538 {
16539   char *p = args;
16540   char *q = NULL;
16541   int val;
16542   char buf[MSG_SIZ];
16543
16544   for (;;) {
16545     while (*p == ' ') p++;
16546     if (*p == NULLCHAR) return;
16547
16548     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16549     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16550     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16551     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16552     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16553     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16554     if (BoolFeature(&p, "reuse", &val, cps)) {
16555       /* Engine can disable reuse, but can't enable it if user said no */
16556       if (!val) cps->reuse = FALSE;
16557       continue;
16558     }
16559     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16560     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16561       if (gameMode == TwoMachinesPlay) {
16562         DisplayTwoMachinesTitle();
16563       } else {
16564         DisplayTitle("");
16565       }
16566       continue;
16567     }
16568     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16569     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16570     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16571     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16572     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16573     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16574     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16575     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16576     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16577     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16578     if (IntFeature(&p, "done", &val, cps)) {
16579       FeatureDone(cps, val);
16580       continue;
16581     }
16582     /* Added by Tord: */
16583     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16584     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16585     /* End of additions by Tord */
16586
16587     /* [HGM] added features: */
16588     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16589     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16590     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16591     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16592     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16593     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16594     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16595     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16596         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16597         FREE(cps->option[cps->nrOptions].name);
16598         cps->option[cps->nrOptions].name = q; q = NULL;
16599         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16600           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16601             SendToProgram(buf, cps);
16602             continue;
16603         }
16604         if(cps->nrOptions >= MAX_OPTIONS) {
16605             cps->nrOptions--;
16606             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16607             DisplayError(buf, 0);
16608         }
16609         continue;
16610     }
16611     /* End of additions by HGM */
16612
16613     /* unknown feature: complain and skip */
16614     q = p;
16615     while (*q && *q != '=') q++;
16616     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16617     SendToProgram(buf, cps);
16618     p = q;
16619     if (*p == '=') {
16620       p++;
16621       if (*p == '\"') {
16622         p++;
16623         while (*p && *p != '\"') p++;
16624         if (*p == '\"') p++;
16625       } else {
16626         while (*p && *p != ' ') p++;
16627       }
16628     }
16629   }
16630
16631 }
16632
16633 void
16634 PeriodicUpdatesEvent (int newState)
16635 {
16636     if (newState == appData.periodicUpdates)
16637       return;
16638
16639     appData.periodicUpdates=newState;
16640
16641     /* Display type changes, so update it now */
16642 //    DisplayAnalysis();
16643
16644     /* Get the ball rolling again... */
16645     if (newState) {
16646         AnalysisPeriodicEvent(1);
16647         StartAnalysisClock();
16648     }
16649 }
16650
16651 void
16652 PonderNextMoveEvent (int newState)
16653 {
16654     if (newState == appData.ponderNextMove) return;
16655     if (gameMode == EditPosition) EditPositionDone(TRUE);
16656     if (newState) {
16657         SendToProgram("hard\n", &first);
16658         if (gameMode == TwoMachinesPlay) {
16659             SendToProgram("hard\n", &second);
16660         }
16661     } else {
16662         SendToProgram("easy\n", &first);
16663         thinkOutput[0] = NULLCHAR;
16664         if (gameMode == TwoMachinesPlay) {
16665             SendToProgram("easy\n", &second);
16666         }
16667     }
16668     appData.ponderNextMove = newState;
16669 }
16670
16671 void
16672 NewSettingEvent (int option, int *feature, char *command, int value)
16673 {
16674     char buf[MSG_SIZ];
16675
16676     if (gameMode == EditPosition) EditPositionDone(TRUE);
16677     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16678     if(feature == NULL || *feature) SendToProgram(buf, &first);
16679     if (gameMode == TwoMachinesPlay) {
16680         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16681     }
16682 }
16683
16684 void
16685 ShowThinkingEvent ()
16686 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16687 {
16688     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16689     int newState = appData.showThinking
16690         // [HGM] thinking: other features now need thinking output as well
16691         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16692
16693     if (oldState == newState) return;
16694     oldState = newState;
16695     if (gameMode == EditPosition) EditPositionDone(TRUE);
16696     if (oldState) {
16697         SendToProgram("post\n", &first);
16698         if (gameMode == TwoMachinesPlay) {
16699             SendToProgram("post\n", &second);
16700         }
16701     } else {
16702         SendToProgram("nopost\n", &first);
16703         thinkOutput[0] = NULLCHAR;
16704         if (gameMode == TwoMachinesPlay) {
16705             SendToProgram("nopost\n", &second);
16706         }
16707     }
16708 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16709 }
16710
16711 void
16712 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16713 {
16714   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16715   if (pr == NoProc) return;
16716   AskQuestion(title, question, replyPrefix, pr);
16717 }
16718
16719 void
16720 TypeInEvent (char firstChar)
16721 {
16722     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16723         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16724         gameMode == AnalyzeMode || gameMode == EditGame ||
16725         gameMode == EditPosition || gameMode == IcsExamining ||
16726         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16727         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16728                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16729                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16730         gameMode == Training) PopUpMoveDialog(firstChar);
16731 }
16732
16733 void
16734 TypeInDoneEvent (char *move)
16735 {
16736         Board board;
16737         int n, fromX, fromY, toX, toY;
16738         char promoChar;
16739         ChessMove moveType;
16740
16741         // [HGM] FENedit
16742         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16743                 EditPositionPasteFEN(move);
16744                 return;
16745         }
16746         // [HGM] movenum: allow move number to be typed in any mode
16747         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16748           ToNrEvent(2*n-1);
16749           return;
16750         }
16751         // undocumented kludge: allow command-line option to be typed in!
16752         // (potentially fatal, and does not implement the effect of the option.)
16753         // should only be used for options that are values on which future decisions will be made,
16754         // and definitely not on options that would be used during initialization.
16755         if(strstr(move, "!!! -") == move) {
16756             ParseArgsFromString(move+4);
16757             return;
16758         }
16759
16760       if (gameMode != EditGame && currentMove != forwardMostMove &&
16761         gameMode != Training) {
16762         DisplayMoveError(_("Displayed move is not current"));
16763       } else {
16764         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16765           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16766         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16767         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16768           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16769           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16770         } else {
16771           DisplayMoveError(_("Could not parse move"));
16772         }
16773       }
16774 }
16775
16776 void
16777 DisplayMove (int moveNumber)
16778 {
16779     char message[MSG_SIZ];
16780     char res[MSG_SIZ];
16781     char cpThinkOutput[MSG_SIZ];
16782
16783     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16784
16785     if (moveNumber == forwardMostMove - 1 ||
16786         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16787
16788         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16789
16790         if (strchr(cpThinkOutput, '\n')) {
16791             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16792         }
16793     } else {
16794         *cpThinkOutput = NULLCHAR;
16795     }
16796
16797     /* [AS] Hide thinking from human user */
16798     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16799         *cpThinkOutput = NULLCHAR;
16800         if( thinkOutput[0] != NULLCHAR ) {
16801             int i;
16802
16803             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16804                 cpThinkOutput[i] = '.';
16805             }
16806             cpThinkOutput[i] = NULLCHAR;
16807             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16808         }
16809     }
16810
16811     if (moveNumber == forwardMostMove - 1 &&
16812         gameInfo.resultDetails != NULL) {
16813         if (gameInfo.resultDetails[0] == NULLCHAR) {
16814           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16815         } else {
16816           snprintf(res, MSG_SIZ, " {%s} %s",
16817                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16818         }
16819     } else {
16820         res[0] = NULLCHAR;
16821     }
16822
16823     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16824         DisplayMessage(res, cpThinkOutput);
16825     } else {
16826       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16827                 WhiteOnMove(moveNumber) ? " " : ".. ",
16828                 parseList[moveNumber], res);
16829         DisplayMessage(message, cpThinkOutput);
16830     }
16831 }
16832
16833 void
16834 DisplayComment (int moveNumber, char *text)
16835 {
16836     char title[MSG_SIZ];
16837
16838     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16839       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16840     } else {
16841       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16842               WhiteOnMove(moveNumber) ? " " : ".. ",
16843               parseList[moveNumber]);
16844     }
16845     if (text != NULL && (appData.autoDisplayComment || commentUp))
16846         CommentPopUp(title, text);
16847 }
16848
16849 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16850  * might be busy thinking or pondering.  It can be omitted if your
16851  * gnuchess is configured to stop thinking immediately on any user
16852  * input.  However, that gnuchess feature depends on the FIONREAD
16853  * ioctl, which does not work properly on some flavors of Unix.
16854  */
16855 void
16856 Attention (ChessProgramState *cps)
16857 {
16858 #if ATTENTION
16859     if (!cps->useSigint) return;
16860     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16861     switch (gameMode) {
16862       case MachinePlaysWhite:
16863       case MachinePlaysBlack:
16864       case TwoMachinesPlay:
16865       case IcsPlayingWhite:
16866       case IcsPlayingBlack:
16867       case AnalyzeMode:
16868       case AnalyzeFile:
16869         /* Skip if we know it isn't thinking */
16870         if (!cps->maybeThinking) return;
16871         if (appData.debugMode)
16872           fprintf(debugFP, "Interrupting %s\n", cps->which);
16873         InterruptChildProcess(cps->pr);
16874         cps->maybeThinking = FALSE;
16875         break;
16876       default:
16877         break;
16878     }
16879 #endif /*ATTENTION*/
16880 }
16881
16882 int
16883 CheckFlags ()
16884 {
16885     if (whiteTimeRemaining <= 0) {
16886         if (!whiteFlag) {
16887             whiteFlag = TRUE;
16888             if (appData.icsActive) {
16889                 if (appData.autoCallFlag &&
16890                     gameMode == IcsPlayingBlack && !blackFlag) {
16891                   SendToICS(ics_prefix);
16892                   SendToICS("flag\n");
16893                 }
16894             } else {
16895                 if (blackFlag) {
16896                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16897                 } else {
16898                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16899                     if (appData.autoCallFlag) {
16900                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16901                         return TRUE;
16902                     }
16903                 }
16904             }
16905         }
16906     }
16907     if (blackTimeRemaining <= 0) {
16908         if (!blackFlag) {
16909             blackFlag = TRUE;
16910             if (appData.icsActive) {
16911                 if (appData.autoCallFlag &&
16912                     gameMode == IcsPlayingWhite && !whiteFlag) {
16913                   SendToICS(ics_prefix);
16914                   SendToICS("flag\n");
16915                 }
16916             } else {
16917                 if (whiteFlag) {
16918                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16919                 } else {
16920                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16921                     if (appData.autoCallFlag) {
16922                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16923                         return TRUE;
16924                     }
16925                 }
16926             }
16927         }
16928     }
16929     return FALSE;
16930 }
16931
16932 void
16933 CheckTimeControl ()
16934 {
16935     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16936         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16937
16938     /*
16939      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16940      */
16941     if ( !WhiteOnMove(forwardMostMove) ) {
16942         /* White made time control */
16943         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16944         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16945         /* [HGM] time odds: correct new time quota for time odds! */
16946                                             / WhitePlayer()->timeOdds;
16947         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16948     } else {
16949         lastBlack -= blackTimeRemaining;
16950         /* Black made time control */
16951         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16952                                             / WhitePlayer()->other->timeOdds;
16953         lastWhite = whiteTimeRemaining;
16954     }
16955 }
16956
16957 void
16958 DisplayBothClocks ()
16959 {
16960     int wom = gameMode == EditPosition ?
16961       !blackPlaysFirst : WhiteOnMove(currentMove);
16962     DisplayWhiteClock(whiteTimeRemaining, wom);
16963     DisplayBlackClock(blackTimeRemaining, !wom);
16964 }
16965
16966
16967 /* Timekeeping seems to be a portability nightmare.  I think everyone
16968    has ftime(), but I'm really not sure, so I'm including some ifdefs
16969    to use other calls if you don't.  Clocks will be less accurate if
16970    you have neither ftime nor gettimeofday.
16971 */
16972
16973 /* VS 2008 requires the #include outside of the function */
16974 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16975 #include <sys/timeb.h>
16976 #endif
16977
16978 /* Get the current time as a TimeMark */
16979 void
16980 GetTimeMark (TimeMark *tm)
16981 {
16982 #if HAVE_GETTIMEOFDAY
16983
16984     struct timeval timeVal;
16985     struct timezone timeZone;
16986
16987     gettimeofday(&timeVal, &timeZone);
16988     tm->sec = (long) timeVal.tv_sec;
16989     tm->ms = (int) (timeVal.tv_usec / 1000L);
16990
16991 #else /*!HAVE_GETTIMEOFDAY*/
16992 #if HAVE_FTIME
16993
16994 // include <sys/timeb.h> / moved to just above start of function
16995     struct timeb timeB;
16996
16997     ftime(&timeB);
16998     tm->sec = (long) timeB.time;
16999     tm->ms = (int) timeB.millitm;
17000
17001 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17002     tm->sec = (long) time(NULL);
17003     tm->ms = 0;
17004 #endif
17005 #endif
17006 }
17007
17008 /* Return the difference in milliseconds between two
17009    time marks.  We assume the difference will fit in a long!
17010 */
17011 long
17012 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17013 {
17014     return 1000L*(tm2->sec - tm1->sec) +
17015            (long) (tm2->ms - tm1->ms);
17016 }
17017
17018
17019 /*
17020  * Code to manage the game clocks.
17021  *
17022  * In tournament play, black starts the clock and then white makes a move.
17023  * We give the human user a slight advantage if he is playing white---the
17024  * clocks don't run until he makes his first move, so it takes zero time.
17025  * Also, we don't account for network lag, so we could get out of sync
17026  * with GNU Chess's clock -- but then, referees are always right.
17027  */
17028
17029 static TimeMark tickStartTM;
17030 static long intendedTickLength;
17031
17032 long
17033 NextTickLength (long timeRemaining)
17034 {
17035     long nominalTickLength, nextTickLength;
17036
17037     if (timeRemaining > 0L && timeRemaining <= 10000L)
17038       nominalTickLength = 100L;
17039     else
17040       nominalTickLength = 1000L;
17041     nextTickLength = timeRemaining % nominalTickLength;
17042     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17043
17044     return nextTickLength;
17045 }
17046
17047 /* Adjust clock one minute up or down */
17048 void
17049 AdjustClock (Boolean which, int dir)
17050 {
17051     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17052     if(which) blackTimeRemaining += 60000*dir;
17053     else      whiteTimeRemaining += 60000*dir;
17054     DisplayBothClocks();
17055     adjustedClock = TRUE;
17056 }
17057
17058 /* Stop clocks and reset to a fresh time control */
17059 void
17060 ResetClocks ()
17061 {
17062     (void) StopClockTimer();
17063     if (appData.icsActive) {
17064         whiteTimeRemaining = blackTimeRemaining = 0;
17065     } else if (searchTime) {
17066         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17067         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17068     } else { /* [HGM] correct new time quote for time odds */
17069         whiteTC = blackTC = fullTimeControlString;
17070         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17071         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17072     }
17073     if (whiteFlag || blackFlag) {
17074         DisplayTitle("");
17075         whiteFlag = blackFlag = FALSE;
17076     }
17077     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17078     DisplayBothClocks();
17079     adjustedClock = FALSE;
17080 }
17081
17082 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17083
17084 /* Decrement running clock by amount of time that has passed */
17085 void
17086 DecrementClocks ()
17087 {
17088     long timeRemaining;
17089     long lastTickLength, fudge;
17090     TimeMark now;
17091
17092     if (!appData.clockMode) return;
17093     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17094
17095     GetTimeMark(&now);
17096
17097     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17098
17099     /* Fudge if we woke up a little too soon */
17100     fudge = intendedTickLength - lastTickLength;
17101     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17102
17103     if (WhiteOnMove(forwardMostMove)) {
17104         if(whiteNPS >= 0) lastTickLength = 0;
17105         timeRemaining = whiteTimeRemaining -= lastTickLength;
17106         if(timeRemaining < 0 && !appData.icsActive) {
17107             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17108             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17109                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17110                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17111             }
17112         }
17113         DisplayWhiteClock(whiteTimeRemaining - fudge,
17114                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17115     } else {
17116         if(blackNPS >= 0) lastTickLength = 0;
17117         timeRemaining = blackTimeRemaining -= lastTickLength;
17118         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17119             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17120             if(suddenDeath) {
17121                 blackStartMove = forwardMostMove;
17122                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17123             }
17124         }
17125         DisplayBlackClock(blackTimeRemaining - fudge,
17126                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17127     }
17128     if (CheckFlags()) return;
17129
17130     if(twoBoards) { // count down secondary board's clocks as well
17131         activePartnerTime -= lastTickLength;
17132         partnerUp = 1;
17133         if(activePartner == 'W')
17134             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17135         else
17136             DisplayBlackClock(activePartnerTime, TRUE);
17137         partnerUp = 0;
17138     }
17139
17140     tickStartTM = now;
17141     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17142     StartClockTimer(intendedTickLength);
17143
17144     /* if the time remaining has fallen below the alarm threshold, sound the
17145      * alarm. if the alarm has sounded and (due to a takeback or time control
17146      * with increment) the time remaining has increased to a level above the
17147      * threshold, reset the alarm so it can sound again.
17148      */
17149
17150     if (appData.icsActive && appData.icsAlarm) {
17151
17152         /* make sure we are dealing with the user's clock */
17153         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17154                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17155            )) return;
17156
17157         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17158             alarmSounded = FALSE;
17159         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17160             PlayAlarmSound();
17161             alarmSounded = TRUE;
17162         }
17163     }
17164 }
17165
17166
17167 /* A player has just moved, so stop the previously running
17168    clock and (if in clock mode) start the other one.
17169    We redisplay both clocks in case we're in ICS mode, because
17170    ICS gives us an update to both clocks after every move.
17171    Note that this routine is called *after* forwardMostMove
17172    is updated, so the last fractional tick must be subtracted
17173    from the color that is *not* on move now.
17174 */
17175 void
17176 SwitchClocks (int newMoveNr)
17177 {
17178     long lastTickLength;
17179     TimeMark now;
17180     int flagged = FALSE;
17181
17182     GetTimeMark(&now);
17183
17184     if (StopClockTimer() && appData.clockMode) {
17185         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17186         if (!WhiteOnMove(forwardMostMove)) {
17187             if(blackNPS >= 0) lastTickLength = 0;
17188             blackTimeRemaining -= lastTickLength;
17189            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17190 //         if(pvInfoList[forwardMostMove].time == -1)
17191                  pvInfoList[forwardMostMove].time =               // use GUI time
17192                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17193         } else {
17194            if(whiteNPS >= 0) lastTickLength = 0;
17195            whiteTimeRemaining -= lastTickLength;
17196            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17197 //         if(pvInfoList[forwardMostMove].time == -1)
17198                  pvInfoList[forwardMostMove].time =
17199                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17200         }
17201         flagged = CheckFlags();
17202     }
17203     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17204     CheckTimeControl();
17205
17206     if (flagged || !appData.clockMode) return;
17207
17208     switch (gameMode) {
17209       case MachinePlaysBlack:
17210       case MachinePlaysWhite:
17211       case BeginningOfGame:
17212         if (pausing) return;
17213         break;
17214
17215       case EditGame:
17216       case PlayFromGameFile:
17217       case IcsExamining:
17218         return;
17219
17220       default:
17221         break;
17222     }
17223
17224     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17225         if(WhiteOnMove(forwardMostMove))
17226              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17227         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17228     }
17229
17230     tickStartTM = now;
17231     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17232       whiteTimeRemaining : blackTimeRemaining);
17233     StartClockTimer(intendedTickLength);
17234 }
17235
17236
17237 /* Stop both clocks */
17238 void
17239 StopClocks ()
17240 {
17241     long lastTickLength;
17242     TimeMark now;
17243
17244     if (!StopClockTimer()) return;
17245     if (!appData.clockMode) return;
17246
17247     GetTimeMark(&now);
17248
17249     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17250     if (WhiteOnMove(forwardMostMove)) {
17251         if(whiteNPS >= 0) lastTickLength = 0;
17252         whiteTimeRemaining -= lastTickLength;
17253         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17254     } else {
17255         if(blackNPS >= 0) lastTickLength = 0;
17256         blackTimeRemaining -= lastTickLength;
17257         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17258     }
17259     CheckFlags();
17260 }
17261
17262 /* Start clock of player on move.  Time may have been reset, so
17263    if clock is already running, stop and restart it. */
17264 void
17265 StartClocks ()
17266 {
17267     (void) StopClockTimer(); /* in case it was running already */
17268     DisplayBothClocks();
17269     if (CheckFlags()) return;
17270
17271     if (!appData.clockMode) return;
17272     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17273
17274     GetTimeMark(&tickStartTM);
17275     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17276       whiteTimeRemaining : blackTimeRemaining);
17277
17278    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17279     whiteNPS = blackNPS = -1;
17280     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17281        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17282         whiteNPS = first.nps;
17283     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17284        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17285         blackNPS = first.nps;
17286     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17287         whiteNPS = second.nps;
17288     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17289         blackNPS = second.nps;
17290     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17291
17292     StartClockTimer(intendedTickLength);
17293 }
17294
17295 char *
17296 TimeString (long ms)
17297 {
17298     long second, minute, hour, day;
17299     char *sign = "";
17300     static char buf[32];
17301
17302     if (ms > 0 && ms <= 9900) {
17303       /* convert milliseconds to tenths, rounding up */
17304       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17305
17306       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17307       return buf;
17308     }
17309
17310     /* convert milliseconds to seconds, rounding up */
17311     /* use floating point to avoid strangeness of integer division
17312        with negative dividends on many machines */
17313     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17314
17315     if (second < 0) {
17316         sign = "-";
17317         second = -second;
17318     }
17319
17320     day = second / (60 * 60 * 24);
17321     second = second % (60 * 60 * 24);
17322     hour = second / (60 * 60);
17323     second = second % (60 * 60);
17324     minute = second / 60;
17325     second = second % 60;
17326
17327     if (day > 0)
17328       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17329               sign, day, hour, minute, second);
17330     else if (hour > 0)
17331       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17332     else
17333       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17334
17335     return buf;
17336 }
17337
17338
17339 /*
17340  * This is necessary because some C libraries aren't ANSI C compliant yet.
17341  */
17342 char *
17343 StrStr (char *string, char *match)
17344 {
17345     int i, length;
17346
17347     length = strlen(match);
17348
17349     for (i = strlen(string) - length; i >= 0; i--, string++)
17350       if (!strncmp(match, string, length))
17351         return string;
17352
17353     return NULL;
17354 }
17355
17356 char *
17357 StrCaseStr (char *string, char *match)
17358 {
17359     int i, j, length;
17360
17361     length = strlen(match);
17362
17363     for (i = strlen(string) - length; i >= 0; i--, string++) {
17364         for (j = 0; j < length; j++) {
17365             if (ToLower(match[j]) != ToLower(string[j]))
17366               break;
17367         }
17368         if (j == length) return string;
17369     }
17370
17371     return NULL;
17372 }
17373
17374 #ifndef _amigados
17375 int
17376 StrCaseCmp (char *s1, char *s2)
17377 {
17378     char c1, c2;
17379
17380     for (;;) {
17381         c1 = ToLower(*s1++);
17382         c2 = ToLower(*s2++);
17383         if (c1 > c2) return 1;
17384         if (c1 < c2) return -1;
17385         if (c1 == NULLCHAR) return 0;
17386     }
17387 }
17388
17389
17390 int
17391 ToLower (int c)
17392 {
17393     return isupper(c) ? tolower(c) : c;
17394 }
17395
17396
17397 int
17398 ToUpper (int c)
17399 {
17400     return islower(c) ? toupper(c) : c;
17401 }
17402 #endif /* !_amigados    */
17403
17404 char *
17405 StrSave (char *s)
17406 {
17407   char *ret;
17408
17409   if ((ret = (char *) malloc(strlen(s) + 1)))
17410     {
17411       safeStrCpy(ret, s, strlen(s)+1);
17412     }
17413   return ret;
17414 }
17415
17416 char *
17417 StrSavePtr (char *s, char **savePtr)
17418 {
17419     if (*savePtr) {
17420         free(*savePtr);
17421     }
17422     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17423       safeStrCpy(*savePtr, s, strlen(s)+1);
17424     }
17425     return(*savePtr);
17426 }
17427
17428 char *
17429 PGNDate ()
17430 {
17431     time_t clock;
17432     struct tm *tm;
17433     char buf[MSG_SIZ];
17434
17435     clock = time((time_t *)NULL);
17436     tm = localtime(&clock);
17437     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17438             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17439     return StrSave(buf);
17440 }
17441
17442
17443 char *
17444 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17445 {
17446     int i, j, fromX, fromY, toX, toY;
17447     int whiteToPlay;
17448     char buf[MSG_SIZ];
17449     char *p, *q;
17450     int emptycount;
17451     ChessSquare piece;
17452
17453     whiteToPlay = (gameMode == EditPosition) ?
17454       !blackPlaysFirst : (move % 2 == 0);
17455     p = buf;
17456
17457     /* Piece placement data */
17458     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17459         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17460         emptycount = 0;
17461         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17462             if (boards[move][i][j] == EmptySquare) {
17463                 emptycount++;
17464             } else { ChessSquare piece = boards[move][i][j];
17465                 if (emptycount > 0) {
17466                     if(emptycount<10) /* [HGM] can be >= 10 */
17467                         *p++ = '0' + emptycount;
17468                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17469                     emptycount = 0;
17470                 }
17471                 if(PieceToChar(piece) == '+') {
17472                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17473                     *p++ = '+';
17474                     piece = (ChessSquare)(DEMOTED piece);
17475                 }
17476                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17477                 if(p[-1] == '~') {
17478                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17479                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17480                     *p++ = '~';
17481                 }
17482             }
17483         }
17484         if (emptycount > 0) {
17485             if(emptycount<10) /* [HGM] can be >= 10 */
17486                 *p++ = '0' + emptycount;
17487             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17488             emptycount = 0;
17489         }
17490         *p++ = '/';
17491     }
17492     *(p - 1) = ' ';
17493
17494     /* [HGM] print Crazyhouse or Shogi holdings */
17495     if( gameInfo.holdingsWidth ) {
17496         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17497         q = p;
17498         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17499             piece = boards[move][i][BOARD_WIDTH-1];
17500             if( piece != EmptySquare )
17501               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17502                   *p++ = PieceToChar(piece);
17503         }
17504         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17505             piece = boards[move][BOARD_HEIGHT-i-1][0];
17506             if( piece != EmptySquare )
17507               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17508                   *p++ = PieceToChar(piece);
17509         }
17510
17511         if( q == p ) *p++ = '-';
17512         *p++ = ']';
17513         *p++ = ' ';
17514     }
17515
17516     /* Active color */
17517     *p++ = whiteToPlay ? 'w' : 'b';
17518     *p++ = ' ';
17519
17520   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17521     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17522   } else {
17523   if(nrCastlingRights) {
17524      q = p;
17525      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17526        /* [HGM] write directly from rights */
17527            if(boards[move][CASTLING][2] != NoRights &&
17528               boards[move][CASTLING][0] != NoRights   )
17529                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17530            if(boards[move][CASTLING][2] != NoRights &&
17531               boards[move][CASTLING][1] != NoRights   )
17532                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17533            if(boards[move][CASTLING][5] != NoRights &&
17534               boards[move][CASTLING][3] != NoRights   )
17535                 *p++ = boards[move][CASTLING][3] + AAA;
17536            if(boards[move][CASTLING][5] != NoRights &&
17537               boards[move][CASTLING][4] != NoRights   )
17538                 *p++ = boards[move][CASTLING][4] + AAA;
17539      } else {
17540
17541         /* [HGM] write true castling rights */
17542         if( nrCastlingRights == 6 ) {
17543             int q, k=0;
17544             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17545                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17546             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17547                  boards[move][CASTLING][2] != NoRights  );
17548             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17549                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17550                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17551                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17552                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17553             }
17554             if(q) *p++ = 'Q';
17555             k = 0;
17556             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17557                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17558             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17559                  boards[move][CASTLING][5] != NoRights  );
17560             if(gameInfo.variant == VariantSChess) {
17561                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17562                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17563                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17564                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17565             }
17566             if(q) *p++ = 'q';
17567         }
17568      }
17569      if (q == p) *p++ = '-'; /* No castling rights */
17570      *p++ = ' ';
17571   }
17572
17573   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17574      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17575      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17576     /* En passant target square */
17577     if (move > backwardMostMove) {
17578         fromX = moveList[move - 1][0] - AAA;
17579         fromY = moveList[move - 1][1] - ONE;
17580         toX = moveList[move - 1][2] - AAA;
17581         toY = moveList[move - 1][3] - ONE;
17582         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17583             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17584             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17585             fromX == toX) {
17586             /* 2-square pawn move just happened */
17587             *p++ = toX + AAA;
17588             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17589         } else {
17590             *p++ = '-';
17591         }
17592     } else if(move == backwardMostMove) {
17593         // [HGM] perhaps we should always do it like this, and forget the above?
17594         if((signed char)boards[move][EP_STATUS] >= 0) {
17595             *p++ = boards[move][EP_STATUS] + AAA;
17596             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17597         } else {
17598             *p++ = '-';
17599         }
17600     } else {
17601         *p++ = '-';
17602     }
17603     *p++ = ' ';
17604   }
17605   }
17606
17607     if(moveCounts)
17608     {   int i = 0, j=move;
17609
17610         /* [HGM] find reversible plies */
17611         if (appData.debugMode) { int k;
17612             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17613             for(k=backwardMostMove; k<=forwardMostMove; k++)
17614                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17615
17616         }
17617
17618         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17619         if( j == backwardMostMove ) i += initialRulePlies;
17620         sprintf(p, "%d ", i);
17621         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17622
17623         /* Fullmove number */
17624         sprintf(p, "%d", (move / 2) + 1);
17625     } else *--p = NULLCHAR;
17626
17627     return StrSave(buf);
17628 }
17629
17630 Boolean
17631 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17632 {
17633     int i, j, k, w=0;
17634     char *p, c;
17635     int emptycount, virgin[BOARD_FILES];
17636     ChessSquare piece;
17637
17638     p = fen;
17639
17640     /* Piece placement data */
17641     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17642         j = 0;
17643         for (;;) {
17644             if (*p == '/' || *p == ' ' || *p == '[' ) {
17645                 if(j > w) w = j;
17646                 emptycount = gameInfo.boardWidth - j;
17647                 while (emptycount--)
17648                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17649                 if (*p == '/') p++;
17650                 else if(autoSize) { // we stumbled unexpectedly into end of board
17651                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17652                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17653                     }
17654                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17655                 }
17656                 break;
17657 #if(BOARD_FILES >= 10)
17658             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17659                 p++; emptycount=10;
17660                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17661                 while (emptycount--)
17662                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17663 #endif
17664             } else if (*p == '*') {
17665                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17666             } else if (isdigit(*p)) {
17667                 emptycount = *p++ - '0';
17668                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17669                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17670                 while (emptycount--)
17671                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17672             } else if (*p == '+' || isalpha(*p)) {
17673                 if (j >= gameInfo.boardWidth) return FALSE;
17674                 if(*p=='+') {
17675                     piece = CharToPiece(*++p);
17676                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17677                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17678                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17679                 } else piece = CharToPiece(*p++);
17680
17681                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17682                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17683                     piece = (ChessSquare) (PROMOTED piece);
17684                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17685                     p++;
17686                 }
17687                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17688             } else {
17689                 return FALSE;
17690             }
17691         }
17692     }
17693     while (*p == '/' || *p == ' ') p++;
17694
17695     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17696
17697     /* [HGM] by default clear Crazyhouse holdings, if present */
17698     if(gameInfo.holdingsWidth) {
17699        for(i=0; i<BOARD_HEIGHT; i++) {
17700            board[i][0]             = EmptySquare; /* black holdings */
17701            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17702            board[i][1]             = (ChessSquare) 0; /* black counts */
17703            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17704        }
17705     }
17706
17707     /* [HGM] look for Crazyhouse holdings here */
17708     while(*p==' ') p++;
17709     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17710         if(*p == '[') p++;
17711         if(*p == '-' ) p++; /* empty holdings */ else {
17712             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17713             /* if we would allow FEN reading to set board size, we would   */
17714             /* have to add holdings and shift the board read so far here   */
17715             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17716                 p++;
17717                 if((int) piece >= (int) BlackPawn ) {
17718                     i = (int)piece - (int)BlackPawn;
17719                     i = PieceToNumber((ChessSquare)i);
17720                     if( i >= gameInfo.holdingsSize ) return FALSE;
17721                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17722                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17723                 } else {
17724                     i = (int)piece - (int)WhitePawn;
17725                     i = PieceToNumber((ChessSquare)i);
17726                     if( i >= gameInfo.holdingsSize ) return FALSE;
17727                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17728                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17729                 }
17730             }
17731         }
17732         if(*p == ']') p++;
17733     }
17734
17735     while(*p == ' ') p++;
17736
17737     /* Active color */
17738     c = *p++;
17739     if(appData.colorNickNames) {
17740       if( c == appData.colorNickNames[0] ) c = 'w'; else
17741       if( c == appData.colorNickNames[1] ) c = 'b';
17742     }
17743     switch (c) {
17744       case 'w':
17745         *blackPlaysFirst = FALSE;
17746         break;
17747       case 'b':
17748         *blackPlaysFirst = TRUE;
17749         break;
17750       default:
17751         return FALSE;
17752     }
17753
17754     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17755     /* return the extra info in global variiables             */
17756
17757     /* set defaults in case FEN is incomplete */
17758     board[EP_STATUS] = EP_UNKNOWN;
17759     for(i=0; i<nrCastlingRights; i++ ) {
17760         board[CASTLING][i] =
17761             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17762     }   /* assume possible unless obviously impossible */
17763     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17764     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17765     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17766                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17767     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17768     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17769     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17770                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17771     FENrulePlies = 0;
17772
17773     while(*p==' ') p++;
17774     if(nrCastlingRights) {
17775       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17776       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17777           /* castling indicator present, so default becomes no castlings */
17778           for(i=0; i<nrCastlingRights; i++ ) {
17779                  board[CASTLING][i] = NoRights;
17780           }
17781       }
17782       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17783              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17784              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17785              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17786         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17787
17788         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17789             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17790             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17791         }
17792         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17793             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17794         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17795                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17796         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17797                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17798         switch(c) {
17799           case'K':
17800               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17801               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17802               board[CASTLING][2] = whiteKingFile;
17803               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17804               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17805               break;
17806           case'Q':
17807               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17808               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17809               board[CASTLING][2] = whiteKingFile;
17810               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17811               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17812               break;
17813           case'k':
17814               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17815               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17816               board[CASTLING][5] = blackKingFile;
17817               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17818               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17819               break;
17820           case'q':
17821               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17822               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17823               board[CASTLING][5] = blackKingFile;
17824               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17825               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17826           case '-':
17827               break;
17828           default: /* FRC castlings */
17829               if(c >= 'a') { /* black rights */
17830                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17831                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17832                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17833                   if(i == BOARD_RGHT) break;
17834                   board[CASTLING][5] = i;
17835                   c -= AAA;
17836                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17837                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17838                   if(c > i)
17839                       board[CASTLING][3] = c;
17840                   else
17841                       board[CASTLING][4] = c;
17842               } else { /* white rights */
17843                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17844                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17845                     if(board[0][i] == WhiteKing) break;
17846                   if(i == BOARD_RGHT) break;
17847                   board[CASTLING][2] = i;
17848                   c -= AAA - 'a' + 'A';
17849                   if(board[0][c] >= WhiteKing) break;
17850                   if(c > i)
17851                       board[CASTLING][0] = c;
17852                   else
17853                       board[CASTLING][1] = c;
17854               }
17855         }
17856       }
17857       for(i=0; i<nrCastlingRights; i++)
17858         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17859       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17860     if (appData.debugMode) {
17861         fprintf(debugFP, "FEN castling rights:");
17862         for(i=0; i<nrCastlingRights; i++)
17863         fprintf(debugFP, " %d", board[CASTLING][i]);
17864         fprintf(debugFP, "\n");
17865     }
17866
17867       while(*p==' ') p++;
17868     }
17869
17870     /* read e.p. field in games that know e.p. capture */
17871     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17872        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17873        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17874       if(*p=='-') {
17875         p++; board[EP_STATUS] = EP_NONE;
17876       } else {
17877          char c = *p++ - AAA;
17878
17879          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17880          if(*p >= '0' && *p <='9') p++;
17881          board[EP_STATUS] = c;
17882       }
17883     }
17884
17885
17886     if(sscanf(p, "%d", &i) == 1) {
17887         FENrulePlies = i; /* 50-move ply counter */
17888         /* (The move number is still ignored)    */
17889     }
17890
17891     return TRUE;
17892 }
17893
17894 void
17895 EditPositionPasteFEN (char *fen)
17896 {
17897   if (fen != NULL) {
17898     Board initial_position;
17899
17900     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17901       DisplayError(_("Bad FEN position in clipboard"), 0);
17902       return ;
17903     } else {
17904       int savedBlackPlaysFirst = blackPlaysFirst;
17905       EditPositionEvent();
17906       blackPlaysFirst = savedBlackPlaysFirst;
17907       CopyBoard(boards[0], initial_position);
17908       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17909       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17910       DisplayBothClocks();
17911       DrawPosition(FALSE, boards[currentMove]);
17912     }
17913   }
17914 }
17915
17916 static char cseq[12] = "\\   ";
17917
17918 Boolean
17919 set_cont_sequence (char *new_seq)
17920 {
17921     int len;
17922     Boolean ret;
17923
17924     // handle bad attempts to set the sequence
17925         if (!new_seq)
17926                 return 0; // acceptable error - no debug
17927
17928     len = strlen(new_seq);
17929     ret = (len > 0) && (len < sizeof(cseq));
17930     if (ret)
17931       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17932     else if (appData.debugMode)
17933       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17934     return ret;
17935 }
17936
17937 /*
17938     reformat a source message so words don't cross the width boundary.  internal
17939     newlines are not removed.  returns the wrapped size (no null character unless
17940     included in source message).  If dest is NULL, only calculate the size required
17941     for the dest buffer.  lp argument indicats line position upon entry, and it's
17942     passed back upon exit.
17943 */
17944 int
17945 wrap (char *dest, char *src, int count, int width, int *lp)
17946 {
17947     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17948
17949     cseq_len = strlen(cseq);
17950     old_line = line = *lp;
17951     ansi = len = clen = 0;
17952
17953     for (i=0; i < count; i++)
17954     {
17955         if (src[i] == '\033')
17956             ansi = 1;
17957
17958         // if we hit the width, back up
17959         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17960         {
17961             // store i & len in case the word is too long
17962             old_i = i, old_len = len;
17963
17964             // find the end of the last word
17965             while (i && src[i] != ' ' && src[i] != '\n')
17966             {
17967                 i--;
17968                 len--;
17969             }
17970
17971             // word too long?  restore i & len before splitting it
17972             if ((old_i-i+clen) >= width)
17973             {
17974                 i = old_i;
17975                 len = old_len;
17976             }
17977
17978             // extra space?
17979             if (i && src[i-1] == ' ')
17980                 len--;
17981
17982             if (src[i] != ' ' && src[i] != '\n')
17983             {
17984                 i--;
17985                 if (len)
17986                     len--;
17987             }
17988
17989             // now append the newline and continuation sequence
17990             if (dest)
17991                 dest[len] = '\n';
17992             len++;
17993             if (dest)
17994                 strncpy(dest+len, cseq, cseq_len);
17995             len += cseq_len;
17996             line = cseq_len;
17997             clen = cseq_len;
17998             continue;
17999         }
18000
18001         if (dest)
18002             dest[len] = src[i];
18003         len++;
18004         if (!ansi)
18005             line++;
18006         if (src[i] == '\n')
18007             line = 0;
18008         if (src[i] == 'm')
18009             ansi = 0;
18010     }
18011     if (dest && appData.debugMode)
18012     {
18013         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18014             count, width, line, len, *lp);
18015         show_bytes(debugFP, src, count);
18016         fprintf(debugFP, "\ndest: ");
18017         show_bytes(debugFP, dest, len);
18018         fprintf(debugFP, "\n");
18019     }
18020     *lp = dest ? line : old_line;
18021
18022     return len;
18023 }
18024
18025 // [HGM] vari: routines for shelving variations
18026 Boolean modeRestore = FALSE;
18027
18028 void
18029 PushInner (int firstMove, int lastMove)
18030 {
18031         int i, j, nrMoves = lastMove - firstMove;
18032
18033         // push current tail of game on stack
18034         savedResult[storedGames] = gameInfo.result;
18035         savedDetails[storedGames] = gameInfo.resultDetails;
18036         gameInfo.resultDetails = NULL;
18037         savedFirst[storedGames] = firstMove;
18038         savedLast [storedGames] = lastMove;
18039         savedFramePtr[storedGames] = framePtr;
18040         framePtr -= nrMoves; // reserve space for the boards
18041         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18042             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18043             for(j=0; j<MOVE_LEN; j++)
18044                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18045             for(j=0; j<2*MOVE_LEN; j++)
18046                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18047             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18048             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18049             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18050             pvInfoList[firstMove+i-1].depth = 0;
18051             commentList[framePtr+i] = commentList[firstMove+i];
18052             commentList[firstMove+i] = NULL;
18053         }
18054
18055         storedGames++;
18056         forwardMostMove = firstMove; // truncate game so we can start variation
18057 }
18058
18059 void
18060 PushTail (int firstMove, int lastMove)
18061 {
18062         if(appData.icsActive) { // only in local mode
18063                 forwardMostMove = currentMove; // mimic old ICS behavior
18064                 return;
18065         }
18066         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18067
18068         PushInner(firstMove, lastMove);
18069         if(storedGames == 1) GreyRevert(FALSE);
18070         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18071 }
18072
18073 void
18074 PopInner (Boolean annotate)
18075 {
18076         int i, j, nrMoves;
18077         char buf[8000], moveBuf[20];
18078
18079         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18080         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18081         nrMoves = savedLast[storedGames] - currentMove;
18082         if(annotate) {
18083                 int cnt = 10;
18084                 if(!WhiteOnMove(currentMove))
18085                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18086                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18087                 for(i=currentMove; i<forwardMostMove; i++) {
18088                         if(WhiteOnMove(i))
18089                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18090                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18091                         strcat(buf, moveBuf);
18092                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18093                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18094                 }
18095                 strcat(buf, ")");
18096         }
18097         for(i=1; i<=nrMoves; i++) { // copy last variation back
18098             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18099             for(j=0; j<MOVE_LEN; j++)
18100                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18101             for(j=0; j<2*MOVE_LEN; j++)
18102                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18103             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18104             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18105             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18106             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18107             commentList[currentMove+i] = commentList[framePtr+i];
18108             commentList[framePtr+i] = NULL;
18109         }
18110         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18111         framePtr = savedFramePtr[storedGames];
18112         gameInfo.result = savedResult[storedGames];
18113         if(gameInfo.resultDetails != NULL) {
18114             free(gameInfo.resultDetails);
18115       }
18116         gameInfo.resultDetails = savedDetails[storedGames];
18117         forwardMostMove = currentMove + nrMoves;
18118 }
18119
18120 Boolean
18121 PopTail (Boolean annotate)
18122 {
18123         if(appData.icsActive) return FALSE; // only in local mode
18124         if(!storedGames) return FALSE; // sanity
18125         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18126
18127         PopInner(annotate);
18128         if(currentMove < forwardMostMove) ForwardEvent(); else
18129         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18130
18131         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18132         return TRUE;
18133 }
18134
18135 void
18136 CleanupTail ()
18137 {       // remove all shelved variations
18138         int i;
18139         for(i=0; i<storedGames; i++) {
18140             if(savedDetails[i])
18141                 free(savedDetails[i]);
18142             savedDetails[i] = NULL;
18143         }
18144         for(i=framePtr; i<MAX_MOVES; i++) {
18145                 if(commentList[i]) free(commentList[i]);
18146                 commentList[i] = NULL;
18147         }
18148         framePtr = MAX_MOVES-1;
18149         storedGames = 0;
18150 }
18151
18152 void
18153 LoadVariation (int index, char *text)
18154 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18155         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18156         int level = 0, move;
18157
18158         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18159         // first find outermost bracketing variation
18160         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18161             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18162                 if(*p == '{') wait = '}'; else
18163                 if(*p == '[') wait = ']'; else
18164                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18165                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18166             }
18167             if(*p == wait) wait = NULLCHAR; // closing ]} found
18168             p++;
18169         }
18170         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18171         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18172         end[1] = NULLCHAR; // clip off comment beyond variation
18173         ToNrEvent(currentMove-1);
18174         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18175         // kludge: use ParsePV() to append variation to game
18176         move = currentMove;
18177         ParsePV(start, TRUE, TRUE);
18178         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18179         ClearPremoveHighlights();
18180         CommentPopDown();
18181         ToNrEvent(currentMove+1);
18182 }
18183
18184 void
18185 LoadTheme ()
18186 {
18187     char *p, *q, buf[MSG_SIZ];
18188     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18189         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18190         ParseArgsFromString(buf);
18191         ActivateTheme(TRUE); // also redo colors
18192         return;
18193     }
18194     p = nickName;
18195     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18196     {
18197         int len;
18198         q = appData.themeNames;
18199         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18200       if(appData.useBitmaps) {
18201         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18202                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18203                 appData.liteBackTextureMode,
18204                 appData.darkBackTextureMode );
18205       } else {
18206         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18207                 Col2Text(2),   // lightSquareColor
18208                 Col2Text(3) ); // darkSquareColor
18209       }
18210       if(appData.useBorder) {
18211         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18212                 appData.border);
18213       } else {
18214         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18215       }
18216       if(appData.useFont) {
18217         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18218                 appData.renderPiecesWithFont,
18219                 appData.fontToPieceTable,
18220                 Col2Text(9),    // appData.fontBackColorWhite
18221                 Col2Text(10) ); // appData.fontForeColorBlack
18222       } else {
18223         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18224                 appData.pieceDirectory);
18225         if(!appData.pieceDirectory[0])
18226           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18227                 Col2Text(0),   // whitePieceColor
18228                 Col2Text(1) ); // blackPieceColor
18229       }
18230       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18231                 Col2Text(4),   // highlightSquareColor
18232                 Col2Text(5) ); // premoveHighlightColor
18233         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18234         if(insert != q) insert[-1] = NULLCHAR;
18235         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18236         if(q)   free(q);
18237     }
18238     ActivateTheme(FALSE);
18239 }