Allow Lion sweep-selection in Chu Chess
[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 && gameInfo.variant != VariantChuChess) 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             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5332             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5333             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5334     if(toX >= 0) {
5335         int victim = boards[currentMove][toY][toX];
5336         boards[currentMove][toY][toX] = promoSweep;
5337         DrawPosition(FALSE, boards[currentMove]);
5338         boards[currentMove][toY][toX] = victim;
5339     } else
5340     ChangeDragPiece(promoSweep);
5341 }
5342
5343 int
5344 PromoScroll (int x, int y)
5345 {
5346   int step = 0;
5347
5348   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5349   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5350   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5351   if(!step) return FALSE;
5352   lastX = x; lastY = y;
5353   if((promoSweep < BlackPawn) == flipView) step = -step;
5354   if(step > 0) selectFlag = 1;
5355   if(!selectFlag) Sweep(step);
5356   return FALSE;
5357 }
5358
5359 void
5360 NextPiece (int step)
5361 {
5362     ChessSquare piece = boards[currentMove][toY][toX];
5363     do {
5364         pieceSweep -= step;
5365         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5366         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5367         if(!step) step = -1;
5368     } while(PieceToChar(pieceSweep) == '.');
5369     boards[currentMove][toY][toX] = pieceSweep;
5370     DrawPosition(FALSE, boards[currentMove]);
5371     boards[currentMove][toY][toX] = piece;
5372 }
5373 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5374 void
5375 AlphaRank (char *move, int n)
5376 {
5377 //    char *p = move, c; int x, y;
5378
5379     if (appData.debugMode) {
5380         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5381     }
5382
5383     if(move[1]=='*' &&
5384        move[2]>='0' && move[2]<='9' &&
5385        move[3]>='a' && move[3]<='x'    ) {
5386         move[1] = '@';
5387         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5388         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5389     } else
5390     if(move[0]>='0' && move[0]<='9' &&
5391        move[1]>='a' && move[1]<='x' &&
5392        move[2]>='0' && move[2]<='9' &&
5393        move[3]>='a' && move[3]<='x'    ) {
5394         /* input move, Shogi -> normal */
5395         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5396         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5397         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5398         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5399     } else
5400     if(move[1]=='@' &&
5401        move[3]>='0' && move[3]<='9' &&
5402        move[2]>='a' && move[2]<='x'    ) {
5403         move[1] = '*';
5404         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5405         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5406     } else
5407     if(
5408        move[0]>='a' && move[0]<='x' &&
5409        move[3]>='0' && move[3]<='9' &&
5410        move[2]>='a' && move[2]<='x'    ) {
5411          /* output move, normal -> Shogi */
5412         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5413         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5414         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5415         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5416         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5417     }
5418     if (appData.debugMode) {
5419         fprintf(debugFP, "   out = '%s'\n", move);
5420     }
5421 }
5422
5423 char yy_textstr[8000];
5424
5425 /* Parser for moves from gnuchess, ICS, or user typein box */
5426 Boolean
5427 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5428 {
5429     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5430
5431     switch (*moveType) {
5432       case WhitePromotion:
5433       case BlackPromotion:
5434       case WhiteNonPromotion:
5435       case BlackNonPromotion:
5436       case NormalMove:
5437       case FirstLeg:
5438       case WhiteCapturesEnPassant:
5439       case BlackCapturesEnPassant:
5440       case WhiteKingSideCastle:
5441       case WhiteQueenSideCastle:
5442       case BlackKingSideCastle:
5443       case BlackQueenSideCastle:
5444       case WhiteKingSideCastleWild:
5445       case WhiteQueenSideCastleWild:
5446       case BlackKingSideCastleWild:
5447       case BlackQueenSideCastleWild:
5448       /* Code added by Tord: */
5449       case WhiteHSideCastleFR:
5450       case WhiteASideCastleFR:
5451       case BlackHSideCastleFR:
5452       case BlackASideCastleFR:
5453       /* End of code added by Tord */
5454       case IllegalMove:         /* bug or odd chess variant */
5455         *fromX = currentMoveString[0] - AAA;
5456         *fromY = currentMoveString[1] - ONE;
5457         *toX = currentMoveString[2] - AAA;
5458         *toY = currentMoveString[3] - ONE;
5459         *promoChar = currentMoveString[4];
5460         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5461             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5462     if (appData.debugMode) {
5463         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5464     }
5465             *fromX = *fromY = *toX = *toY = 0;
5466             return FALSE;
5467         }
5468         if (appData.testLegality) {
5469           return (*moveType != IllegalMove);
5470         } else {
5471           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5472                          // [HGM] lion: if this is a double move we are less critical
5473                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5474         }
5475
5476       case WhiteDrop:
5477       case BlackDrop:
5478         *fromX = *moveType == WhiteDrop ?
5479           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5480           (int) CharToPiece(ToLower(currentMoveString[0]));
5481         *fromY = DROP_RANK;
5482         *toX = currentMoveString[2] - AAA;
5483         *toY = currentMoveString[3] - ONE;
5484         *promoChar = NULLCHAR;
5485         return TRUE;
5486
5487       case AmbiguousMove:
5488       case ImpossibleMove:
5489       case EndOfFile:
5490       case ElapsedTime:
5491       case Comment:
5492       case PGNTag:
5493       case NAG:
5494       case WhiteWins:
5495       case BlackWins:
5496       case GameIsDrawn:
5497       default:
5498     if (appData.debugMode) {
5499         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5500     }
5501         /* bug? */
5502         *fromX = *fromY = *toX = *toY = 0;
5503         *promoChar = NULLCHAR;
5504         return FALSE;
5505     }
5506 }
5507
5508 Boolean pushed = FALSE;
5509 char *lastParseAttempt;
5510
5511 void
5512 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5513 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5514   int fromX, fromY, toX, toY; char promoChar;
5515   ChessMove moveType;
5516   Boolean valid;
5517   int nr = 0;
5518
5519   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5520   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5521     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5522     pushed = TRUE;
5523   }
5524   endPV = forwardMostMove;
5525   do {
5526     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5527     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5528     lastParseAttempt = pv;
5529     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5530     if(!valid && nr == 0 &&
5531        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5532         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5533         // Hande case where played move is different from leading PV move
5534         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5535         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5536         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5537         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5538           endPV += 2; // if position different, keep this
5539           moveList[endPV-1][0] = fromX + AAA;
5540           moveList[endPV-1][1] = fromY + ONE;
5541           moveList[endPV-1][2] = toX + AAA;
5542           moveList[endPV-1][3] = toY + ONE;
5543           parseList[endPV-1][0] = NULLCHAR;
5544           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5545         }
5546       }
5547     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5548     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5549     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5550     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5551         valid++; // allow comments in PV
5552         continue;
5553     }
5554     nr++;
5555     if(endPV+1 > framePtr) break; // no space, truncate
5556     if(!valid) break;
5557     endPV++;
5558     CopyBoard(boards[endPV], boards[endPV-1]);
5559     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5560     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5561     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5562     CoordsToAlgebraic(boards[endPV - 1],
5563                              PosFlags(endPV - 1),
5564                              fromY, fromX, toY, toX, promoChar,
5565                              parseList[endPV - 1]);
5566   } while(valid);
5567   if(atEnd == 2) return; // used hidden, for PV conversion
5568   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5569   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5570   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5571                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5572   DrawPosition(TRUE, boards[currentMove]);
5573 }
5574
5575 int
5576 MultiPV (ChessProgramState *cps)
5577 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5578         int i;
5579         for(i=0; i<cps->nrOptions; i++)
5580             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5581                 return i;
5582         return -1;
5583 }
5584
5585 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5586
5587 Boolean
5588 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5589 {
5590         int startPV, multi, lineStart, origIndex = index;
5591         char *p, buf2[MSG_SIZ];
5592         ChessProgramState *cps = (pane ? &second : &first);
5593
5594         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5595         lastX = x; lastY = y;
5596         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5597         lineStart = startPV = index;
5598         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5599         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5600         index = startPV;
5601         do{ while(buf[index] && buf[index] != '\n') index++;
5602         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5603         buf[index] = 0;
5604         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5605                 int n = cps->option[multi].value;
5606                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5607                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5608                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5609                 cps->option[multi].value = n;
5610                 *start = *end = 0;
5611                 return FALSE;
5612         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5613                 ExcludeClick(origIndex - lineStart);
5614                 return FALSE;
5615         }
5616         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5617         *start = startPV; *end = index-1;
5618         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5619         return TRUE;
5620 }
5621
5622 char *
5623 PvToSAN (char *pv)
5624 {
5625         static char buf[10*MSG_SIZ];
5626         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5627         *buf = NULLCHAR;
5628         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5629         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5630         for(i = forwardMostMove; i<endPV; i++){
5631             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5632             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5633             k += strlen(buf+k);
5634         }
5635         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5636         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5637         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5638         endPV = savedEnd;
5639         return buf;
5640 }
5641
5642 Boolean
5643 LoadPV (int x, int y)
5644 { // called on right mouse click to load PV
5645   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5646   lastX = x; lastY = y;
5647   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5648   extendGame = FALSE;
5649   return TRUE;
5650 }
5651
5652 void
5653 UnLoadPV ()
5654 {
5655   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5656   if(endPV < 0) return;
5657   if(appData.autoCopyPV) CopyFENToClipboard();
5658   endPV = -1;
5659   if(extendGame && currentMove > forwardMostMove) {
5660         Boolean saveAnimate = appData.animate;
5661         if(pushed) {
5662             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5663                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5664             } else storedGames--; // abandon shelved tail of original game
5665         }
5666         pushed = FALSE;
5667         forwardMostMove = currentMove;
5668         currentMove = oldFMM;
5669         appData.animate = FALSE;
5670         ToNrEvent(forwardMostMove);
5671         appData.animate = saveAnimate;
5672   }
5673   currentMove = forwardMostMove;
5674   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5675   ClearPremoveHighlights();
5676   DrawPosition(TRUE, boards[currentMove]);
5677 }
5678
5679 void
5680 MovePV (int x, int y, int h)
5681 { // step through PV based on mouse coordinates (called on mouse move)
5682   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5683
5684   // we must somehow check if right button is still down (might be released off board!)
5685   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5686   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5687   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5688   if(!step) return;
5689   lastX = x; lastY = y;
5690
5691   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5692   if(endPV < 0) return;
5693   if(y < margin) step = 1; else
5694   if(y > h - margin) step = -1;
5695   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5696   currentMove += step;
5697   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5698   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5699                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5700   DrawPosition(FALSE, boards[currentMove]);
5701 }
5702
5703
5704 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5705 // All positions will have equal probability, but the current method will not provide a unique
5706 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5707 #define DARK 1
5708 #define LITE 2
5709 #define ANY 3
5710
5711 int squaresLeft[4];
5712 int piecesLeft[(int)BlackPawn];
5713 int seed, nrOfShuffles;
5714
5715 void
5716 GetPositionNumber ()
5717 {       // sets global variable seed
5718         int i;
5719
5720         seed = appData.defaultFrcPosition;
5721         if(seed < 0) { // randomize based on time for negative FRC position numbers
5722                 for(i=0; i<50; i++) seed += random();
5723                 seed = random() ^ random() >> 8 ^ random() << 8;
5724                 if(seed<0) seed = -seed;
5725         }
5726 }
5727
5728 int
5729 put (Board board, int pieceType, int rank, int n, int shade)
5730 // put the piece on the (n-1)-th empty squares of the given shade
5731 {
5732         int i;
5733
5734         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5735                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5736                         board[rank][i] = (ChessSquare) pieceType;
5737                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5738                         squaresLeft[ANY]--;
5739                         piecesLeft[pieceType]--;
5740                         return i;
5741                 }
5742         }
5743         return -1;
5744 }
5745
5746
5747 void
5748 AddOnePiece (Board board, int pieceType, int rank, int shade)
5749 // calculate where the next piece goes, (any empty square), and put it there
5750 {
5751         int i;
5752
5753         i = seed % squaresLeft[shade];
5754         nrOfShuffles *= squaresLeft[shade];
5755         seed /= squaresLeft[shade];
5756         put(board, pieceType, rank, i, shade);
5757 }
5758
5759 void
5760 AddTwoPieces (Board board, int pieceType, int rank)
5761 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5762 {
5763         int i, n=squaresLeft[ANY], j=n-1, k;
5764
5765         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5766         i = seed % k;  // pick one
5767         nrOfShuffles *= k;
5768         seed /= k;
5769         while(i >= j) i -= j--;
5770         j = n - 1 - j; i += j;
5771         put(board, pieceType, rank, j, ANY);
5772         put(board, pieceType, rank, i, ANY);
5773 }
5774
5775 void
5776 SetUpShuffle (Board board, int number)
5777 {
5778         int i, p, first=1;
5779
5780         GetPositionNumber(); nrOfShuffles = 1;
5781
5782         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5783         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5784         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5785
5786         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5787
5788         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5789             p = (int) board[0][i];
5790             if(p < (int) BlackPawn) piecesLeft[p] ++;
5791             board[0][i] = EmptySquare;
5792         }
5793
5794         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5795             // shuffles restricted to allow normal castling put KRR first
5796             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5797                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5798             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5799                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5800             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5801                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5802             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5803                 put(board, WhiteRook, 0, 0, ANY);
5804             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5805         }
5806
5807         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5808             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5809             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5810                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5811                 while(piecesLeft[p] >= 2) {
5812                     AddOnePiece(board, p, 0, LITE);
5813                     AddOnePiece(board, p, 0, DARK);
5814                 }
5815                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5816             }
5817
5818         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5819             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5820             // but we leave King and Rooks for last, to possibly obey FRC restriction
5821             if(p == (int)WhiteRook) continue;
5822             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5823             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5824         }
5825
5826         // now everything is placed, except perhaps King (Unicorn) and Rooks
5827
5828         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5829             // Last King gets castling rights
5830             while(piecesLeft[(int)WhiteUnicorn]) {
5831                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5832                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5833             }
5834
5835             while(piecesLeft[(int)WhiteKing]) {
5836                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5837                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5838             }
5839
5840
5841         } else {
5842             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5843             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5844         }
5845
5846         // Only Rooks can be left; simply place them all
5847         while(piecesLeft[(int)WhiteRook]) {
5848                 i = put(board, WhiteRook, 0, 0, ANY);
5849                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5850                         if(first) {
5851                                 first=0;
5852                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5853                         }
5854                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5855                 }
5856         }
5857         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5858             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5859         }
5860
5861         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5862 }
5863
5864 int
5865 SetCharTable (char *table, const char * map)
5866 /* [HGM] moved here from winboard.c because of its general usefulness */
5867 /*       Basically a safe strcpy that uses the last character as King */
5868 {
5869     int result = FALSE; int NrPieces;
5870
5871     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5872                     && NrPieces >= 12 && !(NrPieces&1)) {
5873         int i; /* [HGM] Accept even length from 12 to 34 */
5874
5875         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5876         for( i=0; i<NrPieces/2-1; i++ ) {
5877             table[i] = map[i];
5878             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5879         }
5880         table[(int) WhiteKing]  = map[NrPieces/2-1];
5881         table[(int) BlackKing]  = map[NrPieces-1];
5882
5883         result = TRUE;
5884     }
5885
5886     return result;
5887 }
5888
5889 void
5890 Prelude (Board board)
5891 {       // [HGM] superchess: random selection of exo-pieces
5892         int i, j, k; ChessSquare p;
5893         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5894
5895         GetPositionNumber(); // use FRC position number
5896
5897         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5898             SetCharTable(pieceToChar, appData.pieceToCharTable);
5899             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5900                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5901         }
5902
5903         j = seed%4;                 seed /= 4;
5904         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5905         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5906         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5907         j = seed%3 + (seed%3 >= j); seed /= 3;
5908         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5909         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5910         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5911         j = seed%3;                 seed /= 3;
5912         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5913         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5914         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5915         j = seed%2 + (seed%2 >= j); seed /= 2;
5916         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5917         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5918         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5919         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5920         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5921         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5922         put(board, exoPieces[0],    0, 0, ANY);
5923         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5924 }
5925
5926 void
5927 InitPosition (int redraw)
5928 {
5929     ChessSquare (* pieces)[BOARD_FILES];
5930     int i, j, pawnRow=1, pieceRows=1, overrule,
5931     oldx = gameInfo.boardWidth,
5932     oldy = gameInfo.boardHeight,
5933     oldh = gameInfo.holdingsWidth;
5934     static int oldv;
5935
5936     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5937
5938     /* [AS] Initialize pv info list [HGM] and game status */
5939     {
5940         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5941             pvInfoList[i].depth = 0;
5942             boards[i][EP_STATUS] = EP_NONE;
5943             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5944         }
5945
5946         initialRulePlies = 0; /* 50-move counter start */
5947
5948         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5949         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5950     }
5951
5952
5953     /* [HGM] logic here is completely changed. In stead of full positions */
5954     /* the initialized data only consist of the two backranks. The switch */
5955     /* selects which one we will use, which is than copied to the Board   */
5956     /* initialPosition, which for the rest is initialized by Pawns and    */
5957     /* empty squares. This initial position is then copied to boards[0],  */
5958     /* possibly after shuffling, so that it remains available.            */
5959
5960     gameInfo.holdingsWidth = 0; /* default board sizes */
5961     gameInfo.boardWidth    = 8;
5962     gameInfo.boardHeight   = 8;
5963     gameInfo.holdingsSize  = 0;
5964     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5965     for(i=0; i<BOARD_FILES-2; i++)
5966       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5967     initialPosition[EP_STATUS] = EP_NONE;
5968     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5969     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5970          SetCharTable(pieceNickName, appData.pieceNickNames);
5971     else SetCharTable(pieceNickName, "............");
5972     pieces = FIDEArray;
5973
5974     switch (gameInfo.variant) {
5975     case VariantFischeRandom:
5976       shuffleOpenings = TRUE;
5977     default:
5978       break;
5979     case VariantShatranj:
5980       pieces = ShatranjArray;
5981       nrCastlingRights = 0;
5982       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5983       break;
5984     case VariantMakruk:
5985       pieces = makrukArray;
5986       nrCastlingRights = 0;
5987       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5988       break;
5989     case VariantASEAN:
5990       pieces = aseanArray;
5991       nrCastlingRights = 0;
5992       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5993       break;
5994     case VariantTwoKings:
5995       pieces = twoKingsArray;
5996       break;
5997     case VariantGrand:
5998       pieces = GrandArray;
5999       nrCastlingRights = 0;
6000       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6001       gameInfo.boardWidth = 10;
6002       gameInfo.boardHeight = 10;
6003       gameInfo.holdingsSize = 7;
6004       break;
6005     case VariantCapaRandom:
6006       shuffleOpenings = TRUE;
6007     case VariantCapablanca:
6008       pieces = CapablancaArray;
6009       gameInfo.boardWidth = 10;
6010       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6011       break;
6012     case VariantGothic:
6013       pieces = GothicArray;
6014       gameInfo.boardWidth = 10;
6015       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6016       break;
6017     case VariantSChess:
6018       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6019       gameInfo.holdingsSize = 7;
6020       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6021       break;
6022     case VariantJanus:
6023       pieces = JanusArray;
6024       gameInfo.boardWidth = 10;
6025       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6026       nrCastlingRights = 6;
6027         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6028         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6029         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6030         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6031         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6032         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6033       break;
6034     case VariantFalcon:
6035       pieces = FalconArray;
6036       gameInfo.boardWidth = 10;
6037       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6038       break;
6039     case VariantXiangqi:
6040       pieces = XiangqiArray;
6041       gameInfo.boardWidth  = 9;
6042       gameInfo.boardHeight = 10;
6043       nrCastlingRights = 0;
6044       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6045       break;
6046     case VariantShogi:
6047       pieces = ShogiArray;
6048       gameInfo.boardWidth  = 9;
6049       gameInfo.boardHeight = 9;
6050       gameInfo.holdingsSize = 7;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6053       break;
6054     case VariantChu:
6055       pieces = ChuArray; pieceRows = 3;
6056       gameInfo.boardWidth  = 12;
6057       gameInfo.boardHeight = 12;
6058       nrCastlingRights = 0;
6059       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6060                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6061       break;
6062     case VariantCourier:
6063       pieces = CourierArray;
6064       gameInfo.boardWidth  = 12;
6065       nrCastlingRights = 0;
6066       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6067       break;
6068     case VariantKnightmate:
6069       pieces = KnightmateArray;
6070       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6071       break;
6072     case VariantSpartan:
6073       pieces = SpartanArray;
6074       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6075       break;
6076     case VariantLion:
6077       pieces = lionArray;
6078       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6079       break;
6080     case VariantChuChess:
6081       pieces = ChuChessArray;
6082       gameInfo.boardWidth = 10;
6083       gameInfo.boardHeight = 10;
6084       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6085       break;
6086     case VariantFairy:
6087       pieces = fairyArray;
6088       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6089       break;
6090     case VariantGreat:
6091       pieces = GreatArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6094       gameInfo.holdingsSize = 8;
6095       break;
6096     case VariantSuper:
6097       pieces = FIDEArray;
6098       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6099       gameInfo.holdingsSize = 8;
6100       startedFromSetupPosition = TRUE;
6101       break;
6102     case VariantCrazyhouse:
6103     case VariantBughouse:
6104       pieces = FIDEArray;
6105       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6106       gameInfo.holdingsSize = 5;
6107       break;
6108     case VariantWildCastle:
6109       pieces = FIDEArray;
6110       /* !!?shuffle with kings guaranteed to be on d or e file */
6111       shuffleOpenings = 1;
6112       break;
6113     case VariantNoCastle:
6114       pieces = FIDEArray;
6115       nrCastlingRights = 0;
6116       /* !!?unconstrained back-rank shuffle */
6117       shuffleOpenings = 1;
6118       break;
6119     }
6120
6121     overrule = 0;
6122     if(appData.NrFiles >= 0) {
6123         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6124         gameInfo.boardWidth = appData.NrFiles;
6125     }
6126     if(appData.NrRanks >= 0) {
6127         gameInfo.boardHeight = appData.NrRanks;
6128     }
6129     if(appData.holdingsSize >= 0) {
6130         i = appData.holdingsSize;
6131         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6132         gameInfo.holdingsSize = i;
6133     }
6134     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6135     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6136         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6137
6138     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6139     if(pawnRow < 1) pawnRow = 1;
6140     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6141        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6142     if(gameInfo.variant == VariantChu) pawnRow = 3;
6143
6144     /* User pieceToChar list overrules defaults */
6145     if(appData.pieceToCharTable != NULL)
6146         SetCharTable(pieceToChar, appData.pieceToCharTable);
6147
6148     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6149
6150         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6151             s = (ChessSquare) 0; /* account holding counts in guard band */
6152         for( i=0; i<BOARD_HEIGHT; i++ )
6153             initialPosition[i][j] = s;
6154
6155         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6156         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6157         initialPosition[pawnRow][j] = WhitePawn;
6158         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6159         if(gameInfo.variant == VariantXiangqi) {
6160             if(j&1) {
6161                 initialPosition[pawnRow][j] =
6162                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6163                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6164                    initialPosition[2][j] = WhiteCannon;
6165                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6166                 }
6167             }
6168         }
6169         if(gameInfo.variant == VariantChu) {
6170              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6171                initialPosition[pawnRow+1][j] = WhiteCobra,
6172                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6173              for(i=1; i<pieceRows; i++) {
6174                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6175                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6176              }
6177         }
6178         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6179             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6180                initialPosition[0][j] = WhiteRook;
6181                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6182             }
6183         }
6184         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6185     }
6186     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6187     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6188
6189             j=BOARD_LEFT+1;
6190             initialPosition[1][j] = WhiteBishop;
6191             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6192             j=BOARD_RGHT-2;
6193             initialPosition[1][j] = WhiteRook;
6194             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6195     }
6196
6197     if( nrCastlingRights == -1) {
6198         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6199         /*       This sets default castling rights from none to normal corners   */
6200         /* Variants with other castling rights must set them themselves above    */
6201         nrCastlingRights = 6;
6202
6203         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6204         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6205         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6206         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6207         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6208         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6209      }
6210
6211      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6212      if(gameInfo.variant == VariantGreat) { // promotion commoners
6213         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6214         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6215         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6216         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6217      }
6218      if( gameInfo.variant == VariantSChess ) {
6219       initialPosition[1][0] = BlackMarshall;
6220       initialPosition[2][0] = BlackAngel;
6221       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6222       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6223       initialPosition[1][1] = initialPosition[2][1] =
6224       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6225      }
6226   if (appData.debugMode) {
6227     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6228   }
6229     if(shuffleOpenings) {
6230         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6231         startedFromSetupPosition = TRUE;
6232     }
6233     if(startedFromPositionFile) {
6234       /* [HGM] loadPos: use PositionFile for every new game */
6235       CopyBoard(initialPosition, filePosition);
6236       for(i=0; i<nrCastlingRights; i++)
6237           initialRights[i] = filePosition[CASTLING][i];
6238       startedFromSetupPosition = TRUE;
6239     }
6240
6241     CopyBoard(boards[0], initialPosition);
6242
6243     if(oldx != gameInfo.boardWidth ||
6244        oldy != gameInfo.boardHeight ||
6245        oldv != gameInfo.variant ||
6246        oldh != gameInfo.holdingsWidth
6247                                          )
6248             InitDrawingSizes(-2 ,0);
6249
6250     oldv = gameInfo.variant;
6251     if (redraw)
6252       DrawPosition(TRUE, boards[currentMove]);
6253 }
6254
6255 void
6256 SendBoard (ChessProgramState *cps, int moveNum)
6257 {
6258     char message[MSG_SIZ];
6259
6260     if (cps->useSetboard) {
6261       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6262       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6263       SendToProgram(message, cps);
6264       free(fen);
6265
6266     } else {
6267       ChessSquare *bp;
6268       int i, j, left=0, right=BOARD_WIDTH;
6269       /* Kludge to set black to move, avoiding the troublesome and now
6270        * deprecated "black" command.
6271        */
6272       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6273         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6274
6275       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6276
6277       SendToProgram("edit\n", cps);
6278       SendToProgram("#\n", cps);
6279       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6280         bp = &boards[moveNum][i][left];
6281         for (j = left; j < right; j++, bp++) {
6282           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6283           if ((int) *bp < (int) BlackPawn) {
6284             if(j == BOARD_RGHT+1)
6285                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6286             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6287             if(message[0] == '+' || message[0] == '~') {
6288               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6289                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6290                         AAA + j, ONE + i);
6291             }
6292             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6293                 message[1] = BOARD_RGHT   - 1 - j + '1';
6294                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6295             }
6296             SendToProgram(message, cps);
6297           }
6298         }
6299       }
6300
6301       SendToProgram("c\n", cps);
6302       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6303         bp = &boards[moveNum][i][left];
6304         for (j = left; j < right; j++, bp++) {
6305           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6306           if (((int) *bp != (int) EmptySquare)
6307               && ((int) *bp >= (int) BlackPawn)) {
6308             if(j == BOARD_LEFT-2)
6309                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6310             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6311                     AAA + j, ONE + i);
6312             if(message[0] == '+' || message[0] == '~') {
6313               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6314                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6315                         AAA + j, ONE + i);
6316             }
6317             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6318                 message[1] = BOARD_RGHT   - 1 - j + '1';
6319                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6320             }
6321             SendToProgram(message, cps);
6322           }
6323         }
6324       }
6325
6326       SendToProgram(".\n", cps);
6327     }
6328     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6329 }
6330
6331 char exclusionHeader[MSG_SIZ];
6332 int exCnt, excludePtr;
6333 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6334 static Exclusion excluTab[200];
6335 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6336
6337 static void
6338 WriteMap (int s)
6339 {
6340     int j;
6341     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6342     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6343 }
6344
6345 static void
6346 ClearMap ()
6347 {
6348     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6349     excludePtr = 24; exCnt = 0;
6350     WriteMap(0);
6351 }
6352
6353 static void
6354 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6355 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6356     char buf[2*MOVE_LEN], *p;
6357     Exclusion *e = excluTab;
6358     int i;
6359     for(i=0; i<exCnt; i++)
6360         if(e[i].ff == fromX && e[i].fr == fromY &&
6361            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6362     if(i == exCnt) { // was not in exclude list; add it
6363         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6364         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6365             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6366             return; // abort
6367         }
6368         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6369         excludePtr++; e[i].mark = excludePtr++;
6370         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6371         exCnt++;
6372     }
6373     exclusionHeader[e[i].mark] = state;
6374 }
6375
6376 static int
6377 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6378 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6379     char buf[MSG_SIZ];
6380     int j, k;
6381     ChessMove moveType;
6382     if((signed char)promoChar == -1) { // kludge to indicate best move
6383         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6384             return 1; // if unparsable, abort
6385     }
6386     // update exclusion map (resolving toggle by consulting existing state)
6387     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6388     j = k%8; k >>= 3;
6389     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6390     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6391          excludeMap[k] |=   1<<j;
6392     else excludeMap[k] &= ~(1<<j);
6393     // update header
6394     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6395     // inform engine
6396     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6397     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6398     SendToBoth(buf);
6399     return (state == '+');
6400 }
6401
6402 static void
6403 ExcludeClick (int index)
6404 {
6405     int i, j;
6406     Exclusion *e = excluTab;
6407     if(index < 25) { // none, best or tail clicked
6408         if(index < 13) { // none: include all
6409             WriteMap(0); // clear map
6410             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6411             SendToBoth("include all\n"); // and inform engine
6412         } else if(index > 18) { // tail
6413             if(exclusionHeader[19] == '-') { // tail was excluded
6414                 SendToBoth("include all\n");
6415                 WriteMap(0); // clear map completely
6416                 // now re-exclude selected moves
6417                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6418                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6419             } else { // tail was included or in mixed state
6420                 SendToBoth("exclude all\n");
6421                 WriteMap(0xFF); // fill map completely
6422                 // now re-include selected moves
6423                 j = 0; // count them
6424                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6425                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6426                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6427             }
6428         } else { // best
6429             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6430         }
6431     } else {
6432         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6433             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6434             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6435             break;
6436         }
6437     }
6438 }
6439
6440 ChessSquare
6441 DefaultPromoChoice (int white)
6442 {
6443     ChessSquare result;
6444     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6445        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6446         result = WhiteFerz; // no choice
6447     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6448         result= WhiteKing; // in Suicide Q is the last thing we want
6449     else if(gameInfo.variant == VariantSpartan)
6450         result = white ? WhiteQueen : WhiteAngel;
6451     else result = WhiteQueen;
6452     if(!white) result = WHITE_TO_BLACK result;
6453     return result;
6454 }
6455
6456 static int autoQueen; // [HGM] oneclick
6457
6458 int
6459 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6460 {
6461     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6462     /* [HGM] add Shogi promotions */
6463     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6464     ChessSquare piece, partner;
6465     ChessMove moveType;
6466     Boolean premove;
6467
6468     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6469     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6470
6471     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6472       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6473         return FALSE;
6474
6475     piece = boards[currentMove][fromY][fromX];
6476     if(gameInfo.variant == VariantChu) {
6477         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6478         promotionZoneSize = BOARD_HEIGHT/3;
6479         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6480     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6481         promotionZoneSize = BOARD_HEIGHT/3;
6482         highestPromotingPiece = (int)WhiteAlfil;
6483     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6484         promotionZoneSize = 3;
6485     }
6486
6487     // Treat Lance as Pawn when it is not representing Amazon
6488     if(gameInfo.variant != VariantSuper) {
6489         if(piece == WhiteLance) piece = WhitePawn; else
6490         if(piece == BlackLance) piece = BlackPawn;
6491     }
6492
6493     // next weed out all moves that do not touch the promotion zone at all
6494     if((int)piece >= BlackPawn) {
6495         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6496              return FALSE;
6497         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6498         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6499     } else {
6500         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6501            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6502         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6503              return FALSE;
6504     }
6505
6506     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6507
6508     // weed out mandatory Shogi promotions
6509     if(gameInfo.variant == VariantShogi) {
6510         if(piece >= BlackPawn) {
6511             if(toY == 0 && piece == BlackPawn ||
6512                toY == 0 && piece == BlackQueen ||
6513                toY <= 1 && piece == BlackKnight) {
6514                 *promoChoice = '+';
6515                 return FALSE;
6516             }
6517         } else {
6518             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6519                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6520                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6521                 *promoChoice = '+';
6522                 return FALSE;
6523             }
6524         }
6525     }
6526
6527     // weed out obviously illegal Pawn moves
6528     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6529         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6530         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6531         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6532         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6533         // note we are not allowed to test for valid (non-)capture, due to premove
6534     }
6535
6536     // we either have a choice what to promote to, or (in Shogi) whether to promote
6537     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6538        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6539         *promoChoice = PieceToChar(BlackFerz);  // no choice
6540         return FALSE;
6541     }
6542     // no sense asking what we must promote to if it is going to explode...
6543     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6544         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6545         return FALSE;
6546     }
6547     // give caller the default choice even if we will not make it
6548     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6549     partner = piece; // pieces can promote if the pieceToCharTable says so
6550     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6551     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6552     if(        sweepSelect && gameInfo.variant != VariantGreat
6553                            && gameInfo.variant != VariantGrand
6554                            && gameInfo.variant != VariantSuper) return FALSE;
6555     if(autoQueen) return FALSE; // predetermined
6556
6557     // suppress promotion popup on illegal moves that are not premoves
6558     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6559               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6560     if(appData.testLegality && !premove) {
6561         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6562                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6563         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6564         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6565             return FALSE;
6566     }
6567
6568     return TRUE;
6569 }
6570
6571 int
6572 InPalace (int row, int column)
6573 {   /* [HGM] for Xiangqi */
6574     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6575          column < (BOARD_WIDTH + 4)/2 &&
6576          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6577     return FALSE;
6578 }
6579
6580 int
6581 PieceForSquare (int x, int y)
6582 {
6583   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6584      return -1;
6585   else
6586      return boards[currentMove][y][x];
6587 }
6588
6589 int
6590 OKToStartUserMove (int x, int y)
6591 {
6592     ChessSquare from_piece;
6593     int white_piece;
6594
6595     if (matchMode) return FALSE;
6596     if (gameMode == EditPosition) return TRUE;
6597
6598     if (x >= 0 && y >= 0)
6599       from_piece = boards[currentMove][y][x];
6600     else
6601       from_piece = EmptySquare;
6602
6603     if (from_piece == EmptySquare) return FALSE;
6604
6605     white_piece = (int)from_piece >= (int)WhitePawn &&
6606       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6607
6608     switch (gameMode) {
6609       case AnalyzeFile:
6610       case TwoMachinesPlay:
6611       case EndOfGame:
6612         return FALSE;
6613
6614       case IcsObserving:
6615       case IcsIdle:
6616         return FALSE;
6617
6618       case MachinePlaysWhite:
6619       case IcsPlayingBlack:
6620         if (appData.zippyPlay) return FALSE;
6621         if (white_piece) {
6622             DisplayMoveError(_("You are playing Black"));
6623             return FALSE;
6624         }
6625         break;
6626
6627       case MachinePlaysBlack:
6628       case IcsPlayingWhite:
6629         if (appData.zippyPlay) return FALSE;
6630         if (!white_piece) {
6631             DisplayMoveError(_("You are playing White"));
6632             return FALSE;
6633         }
6634         break;
6635
6636       case PlayFromGameFile:
6637             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6638       case EditGame:
6639         if (!white_piece && WhiteOnMove(currentMove)) {
6640             DisplayMoveError(_("It is White's turn"));
6641             return FALSE;
6642         }
6643         if (white_piece && !WhiteOnMove(currentMove)) {
6644             DisplayMoveError(_("It is Black's turn"));
6645             return FALSE;
6646         }
6647         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6648             /* Editing correspondence game history */
6649             /* Could disallow this or prompt for confirmation */
6650             cmailOldMove = -1;
6651         }
6652         break;
6653
6654       case BeginningOfGame:
6655         if (appData.icsActive) return FALSE;
6656         if (!appData.noChessProgram) {
6657             if (!white_piece) {
6658                 DisplayMoveError(_("You are playing White"));
6659                 return FALSE;
6660             }
6661         }
6662         break;
6663
6664       case Training:
6665         if (!white_piece && WhiteOnMove(currentMove)) {
6666             DisplayMoveError(_("It is White's turn"));
6667             return FALSE;
6668         }
6669         if (white_piece && !WhiteOnMove(currentMove)) {
6670             DisplayMoveError(_("It is Black's turn"));
6671             return FALSE;
6672         }
6673         break;
6674
6675       default:
6676       case IcsExamining:
6677         break;
6678     }
6679     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6680         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6681         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6682         && gameMode != AnalyzeFile && gameMode != Training) {
6683         DisplayMoveError(_("Displayed position is not current"));
6684         return FALSE;
6685     }
6686     return TRUE;
6687 }
6688
6689 Boolean
6690 OnlyMove (int *x, int *y, Boolean captures)
6691 {
6692     DisambiguateClosure cl;
6693     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6694     switch(gameMode) {
6695       case MachinePlaysBlack:
6696       case IcsPlayingWhite:
6697       case BeginningOfGame:
6698         if(!WhiteOnMove(currentMove)) return FALSE;
6699         break;
6700       case MachinePlaysWhite:
6701       case IcsPlayingBlack:
6702         if(WhiteOnMove(currentMove)) return FALSE;
6703         break;
6704       case EditGame:
6705         break;
6706       default:
6707         return FALSE;
6708     }
6709     cl.pieceIn = EmptySquare;
6710     cl.rfIn = *y;
6711     cl.ffIn = *x;
6712     cl.rtIn = -1;
6713     cl.ftIn = -1;
6714     cl.promoCharIn = NULLCHAR;
6715     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6716     if( cl.kind == NormalMove ||
6717         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6718         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6719         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6720       fromX = cl.ff;
6721       fromY = cl.rf;
6722       *x = cl.ft;
6723       *y = cl.rt;
6724       return TRUE;
6725     }
6726     if(cl.kind != ImpossibleMove) return FALSE;
6727     cl.pieceIn = EmptySquare;
6728     cl.rfIn = -1;
6729     cl.ffIn = -1;
6730     cl.rtIn = *y;
6731     cl.ftIn = *x;
6732     cl.promoCharIn = NULLCHAR;
6733     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6734     if( cl.kind == NormalMove ||
6735         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6736         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6737         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6738       fromX = cl.ff;
6739       fromY = cl.rf;
6740       *x = cl.ft;
6741       *y = cl.rt;
6742       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6743       return TRUE;
6744     }
6745     return FALSE;
6746 }
6747
6748 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6749 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6750 int lastLoadGameUseList = FALSE;
6751 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6752 ChessMove lastLoadGameStart = EndOfFile;
6753 int doubleClick;
6754
6755 void
6756 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6757 {
6758     ChessMove moveType;
6759     ChessSquare pup;
6760     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6761
6762     /* Check if the user is playing in turn.  This is complicated because we
6763        let the user "pick up" a piece before it is his turn.  So the piece he
6764        tried to pick up may have been captured by the time he puts it down!
6765        Therefore we use the color the user is supposed to be playing in this
6766        test, not the color of the piece that is currently on the starting
6767        square---except in EditGame mode, where the user is playing both
6768        sides; fortunately there the capture race can't happen.  (It can
6769        now happen in IcsExamining mode, but that's just too bad.  The user
6770        will get a somewhat confusing message in that case.)
6771        */
6772
6773     switch (gameMode) {
6774       case AnalyzeFile:
6775       case TwoMachinesPlay:
6776       case EndOfGame:
6777       case IcsObserving:
6778       case IcsIdle:
6779         /* We switched into a game mode where moves are not accepted,
6780            perhaps while the mouse button was down. */
6781         return;
6782
6783       case MachinePlaysWhite:
6784         /* User is moving for Black */
6785         if (WhiteOnMove(currentMove)) {
6786             DisplayMoveError(_("It is White's turn"));
6787             return;
6788         }
6789         break;
6790
6791       case MachinePlaysBlack:
6792         /* User is moving for White */
6793         if (!WhiteOnMove(currentMove)) {
6794             DisplayMoveError(_("It is Black's turn"));
6795             return;
6796         }
6797         break;
6798
6799       case PlayFromGameFile:
6800             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6801       case EditGame:
6802       case IcsExamining:
6803       case BeginningOfGame:
6804       case AnalyzeMode:
6805       case Training:
6806         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6807         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6808             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6809             /* User is moving for Black */
6810             if (WhiteOnMove(currentMove)) {
6811                 DisplayMoveError(_("It is White's turn"));
6812                 return;
6813             }
6814         } else {
6815             /* User is moving for White */
6816             if (!WhiteOnMove(currentMove)) {
6817                 DisplayMoveError(_("It is Black's turn"));
6818                 return;
6819             }
6820         }
6821         break;
6822
6823       case IcsPlayingBlack:
6824         /* User is moving for Black */
6825         if (WhiteOnMove(currentMove)) {
6826             if (!appData.premove) {
6827                 DisplayMoveError(_("It is White's turn"));
6828             } else if (toX >= 0 && toY >= 0) {
6829                 premoveToX = toX;
6830                 premoveToY = toY;
6831                 premoveFromX = fromX;
6832                 premoveFromY = fromY;
6833                 premovePromoChar = promoChar;
6834                 gotPremove = 1;
6835                 if (appData.debugMode)
6836                     fprintf(debugFP, "Got premove: fromX %d,"
6837                             "fromY %d, toX %d, toY %d\n",
6838                             fromX, fromY, toX, toY);
6839             }
6840             return;
6841         }
6842         break;
6843
6844       case IcsPlayingWhite:
6845         /* User is moving for White */
6846         if (!WhiteOnMove(currentMove)) {
6847             if (!appData.premove) {
6848                 DisplayMoveError(_("It is Black's turn"));
6849             } else if (toX >= 0 && toY >= 0) {
6850                 premoveToX = toX;
6851                 premoveToY = toY;
6852                 premoveFromX = fromX;
6853                 premoveFromY = fromY;
6854                 premovePromoChar = promoChar;
6855                 gotPremove = 1;
6856                 if (appData.debugMode)
6857                     fprintf(debugFP, "Got premove: fromX %d,"
6858                             "fromY %d, toX %d, toY %d\n",
6859                             fromX, fromY, toX, toY);
6860             }
6861             return;
6862         }
6863         break;
6864
6865       default:
6866         break;
6867
6868       case EditPosition:
6869         /* EditPosition, empty square, or different color piece;
6870            click-click move is possible */
6871         if (toX == -2 || toY == -2) {
6872             boards[0][fromY][fromX] = EmptySquare;
6873             DrawPosition(FALSE, boards[currentMove]);
6874             return;
6875         } else if (toX >= 0 && toY >= 0) {
6876             boards[0][toY][toX] = boards[0][fromY][fromX];
6877             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6878                 if(boards[0][fromY][0] != EmptySquare) {
6879                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6880                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6881                 }
6882             } else
6883             if(fromX == BOARD_RGHT+1) {
6884                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6885                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6886                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6887                 }
6888             } else
6889             boards[0][fromY][fromX] = gatingPiece;
6890             DrawPosition(FALSE, boards[currentMove]);
6891             return;
6892         }
6893         return;
6894     }
6895
6896     if(toX < 0 || toY < 0) return;
6897     pup = boards[currentMove][toY][toX];
6898
6899     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6900     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6901          if( pup != EmptySquare ) return;
6902          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6903            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6904                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6905            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6906            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6907            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6908            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6909          fromY = DROP_RANK;
6910     }
6911
6912     /* [HGM] always test for legality, to get promotion info */
6913     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6914                                          fromY, fromX, toY, toX, promoChar);
6915
6916     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6917
6918     /* [HGM] but possibly ignore an IllegalMove result */
6919     if (appData.testLegality) {
6920         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6921             DisplayMoveError(_("Illegal move"));
6922             return;
6923         }
6924     }
6925
6926     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6927         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6928              ClearPremoveHighlights(); // was included
6929         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6930         return;
6931     }
6932
6933     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6934 }
6935
6936 /* Common tail of UserMoveEvent and DropMenuEvent */
6937 int
6938 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6939 {
6940     char *bookHit = 0;
6941
6942     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6943         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6944         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6945         if(WhiteOnMove(currentMove)) {
6946             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6947         } else {
6948             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6949         }
6950     }
6951
6952     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6953        move type in caller when we know the move is a legal promotion */
6954     if(moveType == NormalMove && promoChar)
6955         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6956
6957     /* [HGM] <popupFix> The following if has been moved here from
6958        UserMoveEvent(). Because it seemed to belong here (why not allow
6959        piece drops in training games?), and because it can only be
6960        performed after it is known to what we promote. */
6961     if (gameMode == Training) {
6962       /* compare the move played on the board to the next move in the
6963        * game. If they match, display the move and the opponent's response.
6964        * If they don't match, display an error message.
6965        */
6966       int saveAnimate;
6967       Board testBoard;
6968       CopyBoard(testBoard, boards[currentMove]);
6969       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6970
6971       if (CompareBoards(testBoard, boards[currentMove+1])) {
6972         ForwardInner(currentMove+1);
6973
6974         /* Autoplay the opponent's response.
6975          * if appData.animate was TRUE when Training mode was entered,
6976          * the response will be animated.
6977          */
6978         saveAnimate = appData.animate;
6979         appData.animate = animateTraining;
6980         ForwardInner(currentMove+1);
6981         appData.animate = saveAnimate;
6982
6983         /* check for the end of the game */
6984         if (currentMove >= forwardMostMove) {
6985           gameMode = PlayFromGameFile;
6986           ModeHighlight();
6987           SetTrainingModeOff();
6988           DisplayInformation(_("End of game"));
6989         }
6990       } else {
6991         DisplayError(_("Incorrect move"), 0);
6992       }
6993       return 1;
6994     }
6995
6996   /* Ok, now we know that the move is good, so we can kill
6997      the previous line in Analysis Mode */
6998   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6999                                 && currentMove < forwardMostMove) {
7000     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7001     else forwardMostMove = currentMove;
7002   }
7003
7004   ClearMap();
7005
7006   /* If we need the chess program but it's dead, restart it */
7007   ResurrectChessProgram();
7008
7009   /* A user move restarts a paused game*/
7010   if (pausing)
7011     PauseEvent();
7012
7013   thinkOutput[0] = NULLCHAR;
7014
7015   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7016
7017   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7018     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7019     return 1;
7020   }
7021
7022   if (gameMode == BeginningOfGame) {
7023     if (appData.noChessProgram) {
7024       gameMode = EditGame;
7025       SetGameInfo();
7026     } else {
7027       char buf[MSG_SIZ];
7028       gameMode = MachinePlaysBlack;
7029       StartClocks();
7030       SetGameInfo();
7031       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7032       DisplayTitle(buf);
7033       if (first.sendName) {
7034         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7035         SendToProgram(buf, &first);
7036       }
7037       StartClocks();
7038     }
7039     ModeHighlight();
7040   }
7041
7042   /* Relay move to ICS or chess engine */
7043   if (appData.icsActive) {
7044     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7045         gameMode == IcsExamining) {
7046       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7047         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7048         SendToICS("draw ");
7049         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7050       }
7051       // also send plain move, in case ICS does not understand atomic claims
7052       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7053       ics_user_moved = 1;
7054     }
7055   } else {
7056     if (first.sendTime && (gameMode == BeginningOfGame ||
7057                            gameMode == MachinePlaysWhite ||
7058                            gameMode == MachinePlaysBlack)) {
7059       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7060     }
7061     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7062          // [HGM] book: if program might be playing, let it use book
7063         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7064         first.maybeThinking = TRUE;
7065     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7066         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7067         SendBoard(&first, currentMove+1);
7068         if(second.analyzing) {
7069             if(!second.useSetboard) SendToProgram("undo\n", &second);
7070             SendBoard(&second, currentMove+1);
7071         }
7072     } else {
7073         SendMoveToProgram(forwardMostMove-1, &first);
7074         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7075     }
7076     if (currentMove == cmailOldMove + 1) {
7077       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7078     }
7079   }
7080
7081   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7082
7083   switch (gameMode) {
7084   case EditGame:
7085     if(appData.testLegality)
7086     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7087     case MT_NONE:
7088     case MT_CHECK:
7089       break;
7090     case MT_CHECKMATE:
7091     case MT_STAINMATE:
7092       if (WhiteOnMove(currentMove)) {
7093         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7094       } else {
7095         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7096       }
7097       break;
7098     case MT_STALEMATE:
7099       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7100       break;
7101     }
7102     break;
7103
7104   case MachinePlaysBlack:
7105   case MachinePlaysWhite:
7106     /* disable certain menu options while machine is thinking */
7107     SetMachineThinkingEnables();
7108     break;
7109
7110   default:
7111     break;
7112   }
7113
7114   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7115   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7116
7117   if(bookHit) { // [HGM] book: simulate book reply
7118         static char bookMove[MSG_SIZ]; // a bit generous?
7119
7120         programStats.nodes = programStats.depth = programStats.time =
7121         programStats.score = programStats.got_only_move = 0;
7122         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7123
7124         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7125         strcat(bookMove, bookHit);
7126         HandleMachineMove(bookMove, &first);
7127   }
7128   return 1;
7129 }
7130
7131 void
7132 MarkByFEN(char *fen)
7133 {
7134         int r, f;
7135         if(!appData.markers || !appData.highlightDragging) return;
7136         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7137         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7138         while(*fen) {
7139             int s = 0;
7140             marker[r][f] = 0;
7141             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7142             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7143             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7144             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7145             if(*fen == 'T') marker[r][f++] = 0; else
7146             if(*fen == 'Y') marker[r][f++] = 1; else
7147             if(*fen == 'G') marker[r][f++] = 3; else
7148             if(*fen == 'B') marker[r][f++] = 4; else
7149             if(*fen == 'C') marker[r][f++] = 5; else
7150             if(*fen == 'M') marker[r][f++] = 6; else
7151             if(*fen == 'W') marker[r][f++] = 7; else
7152             if(*fen == 'D') marker[r][f++] = 8; else
7153             if(*fen == 'R') marker[r][f++] = 2; else {
7154                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7155               f += s; fen -= s>0;
7156             }
7157             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7158             if(r < 0) break;
7159             fen++;
7160         }
7161         DrawPosition(TRUE, NULL);
7162 }
7163
7164 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7165
7166 void
7167 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7168 {
7169     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7170     Markers *m = (Markers *) closure;
7171     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7172         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7173                          || kind == WhiteCapturesEnPassant
7174                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7175     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7176 }
7177
7178 void
7179 MarkTargetSquares (int clear)
7180 {
7181   int x, y, sum=0;
7182   if(clear) { // no reason to ever suppress clearing
7183     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;
7184     if(!sum) return; // nothing was cleared,no redraw needed
7185   } else {
7186     int capt = 0;
7187     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7188        !appData.testLegality || gameMode == EditPosition) return;
7189     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7190     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7191       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7192       if(capt)
7193       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7194     }
7195   }
7196   DrawPosition(FALSE, NULL);
7197 }
7198
7199 int
7200 Explode (Board board, int fromX, int fromY, int toX, int toY)
7201 {
7202     if(gameInfo.variant == VariantAtomic &&
7203        (board[toY][toX] != EmptySquare ||                     // capture?
7204         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7205                          board[fromY][fromX] == BlackPawn   )
7206       )) {
7207         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7208         return TRUE;
7209     }
7210     return FALSE;
7211 }
7212
7213 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7214
7215 int
7216 CanPromote (ChessSquare piece, int y)
7217 {
7218         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7219         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7220         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7221         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7222            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7223            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7224          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7225         return (piece == BlackPawn && y <= zone ||
7226                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7227                 piece == BlackLance && y == 1 ||
7228                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7229 }
7230
7231 void
7232 HoverEvent (int xPix, int yPix, int x, int y)
7233 {
7234         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7235         int r, f;
7236         if(!first.highlight) return;
7237         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7238         if(x == oldX && y == oldY) return; // only do something if we enter new square
7239         oldFromX = fromX; oldFromY = fromY;
7240         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7241           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7242             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7243         else if(oldX != x || oldY != y) {
7244           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7245           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7246             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7247           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7248             char buf[MSG_SIZ];
7249             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7250             SendToProgram(buf, &first);
7251           }
7252           oldX = x; oldY = y;
7253 //        SetHighlights(fromX, fromY, x, y);
7254         }
7255 }
7256
7257 void ReportClick(char *action, int x, int y)
7258 {
7259         char buf[MSG_SIZ]; // Inform engine of what user does
7260         int r, f;
7261         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7262           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7263         if(!first.highlight || gameMode == EditPosition) return;
7264         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7265         SendToProgram(buf, &first);
7266 }
7267
7268 void
7269 LeftClick (ClickType clickType, int xPix, int yPix)
7270 {
7271     int x, y;
7272     Boolean saveAnimate;
7273     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7274     char promoChoice = NULLCHAR;
7275     ChessSquare piece;
7276     static TimeMark lastClickTime, prevClickTime;
7277
7278     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7279
7280     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7281
7282     if (clickType == Press) ErrorPopDown();
7283     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7284
7285     x = EventToSquare(xPix, BOARD_WIDTH);
7286     y = EventToSquare(yPix, BOARD_HEIGHT);
7287     if (!flipView && y >= 0) {
7288         y = BOARD_HEIGHT - 1 - y;
7289     }
7290     if (flipView && x >= 0) {
7291         x = BOARD_WIDTH - 1 - x;
7292     }
7293
7294     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7295         defaultPromoChoice = promoSweep;
7296         promoSweep = EmptySquare;   // terminate sweep
7297         promoDefaultAltered = TRUE;
7298         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7299     }
7300
7301     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7302         if(clickType == Release) return; // ignore upclick of click-click destination
7303         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7304         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7305         if(gameInfo.holdingsWidth &&
7306                 (WhiteOnMove(currentMove)
7307                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7308                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7309             // click in right holdings, for determining promotion piece
7310             ChessSquare p = boards[currentMove][y][x];
7311             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7312             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7313             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7314                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7315                 fromX = fromY = -1;
7316                 return;
7317             }
7318         }
7319         DrawPosition(FALSE, boards[currentMove]);
7320         return;
7321     }
7322
7323     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7324     if(clickType == Press
7325             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7326               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7327               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7328         return;
7329
7330     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7331         // could be static click on premove from-square: abort premove
7332         gotPremove = 0;
7333         ClearPremoveHighlights();
7334     }
7335
7336     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7337         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7338
7339     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7340         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7341                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7342         defaultPromoChoice = DefaultPromoChoice(side);
7343     }
7344
7345     autoQueen = appData.alwaysPromoteToQueen;
7346
7347     if (fromX == -1) {
7348       int originalY = y;
7349       gatingPiece = EmptySquare;
7350       if (clickType != Press) {
7351         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7352             DragPieceEnd(xPix, yPix); dragging = 0;
7353             DrawPosition(FALSE, NULL);
7354         }
7355         return;
7356       }
7357       doubleClick = FALSE;
7358       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7359         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7360       }
7361       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7362       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7363          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7364          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7365             /* First square */
7366             if (OKToStartUserMove(fromX, fromY)) {
7367                 second = 0;
7368                 ReportClick("lift", x, y);
7369                 MarkTargetSquares(0);
7370                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7371                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7372                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7373                     promoSweep = defaultPromoChoice;
7374                     selectFlag = 0; lastX = xPix; lastY = yPix;
7375                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7376                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7377                 }
7378                 if (appData.highlightDragging) {
7379                     SetHighlights(fromX, fromY, -1, -1);
7380                 } else {
7381                     ClearHighlights();
7382                 }
7383             } else fromX = fromY = -1;
7384             return;
7385         }
7386     }
7387
7388     /* fromX != -1 */
7389     if (clickType == Press && gameMode != EditPosition) {
7390         ChessSquare fromP;
7391         ChessSquare toP;
7392         int frc;
7393
7394         // ignore off-board to clicks
7395         if(y < 0 || x < 0) return;
7396
7397         /* Check if clicking again on the same color piece */
7398         fromP = boards[currentMove][fromY][fromX];
7399         toP = boards[currentMove][y][x];
7400         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7401         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7402            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7403              WhitePawn <= toP && toP <= WhiteKing &&
7404              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7405              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7406             (BlackPawn <= fromP && fromP <= BlackKing &&
7407              BlackPawn <= toP && toP <= BlackKing &&
7408              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7409              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7410             /* Clicked again on same color piece -- changed his mind */
7411             second = (x == fromX && y == fromY);
7412             killX = killY = -1;
7413             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7414                 second = FALSE; // first double-click rather than scond click
7415                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7416             }
7417             promoDefaultAltered = FALSE;
7418             MarkTargetSquares(1);
7419            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7420             if (appData.highlightDragging) {
7421                 SetHighlights(x, y, -1, -1);
7422             } else {
7423                 ClearHighlights();
7424             }
7425             if (OKToStartUserMove(x, y)) {
7426                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7427                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7428                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7429                  gatingPiece = boards[currentMove][fromY][fromX];
7430                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7431                 fromX = x;
7432                 fromY = y; dragging = 1;
7433                 ReportClick("lift", x, y);
7434                 MarkTargetSquares(0);
7435                 DragPieceBegin(xPix, yPix, FALSE);
7436                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7437                     promoSweep = defaultPromoChoice;
7438                     selectFlag = 0; lastX = xPix; lastY = yPix;
7439                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7440                 }
7441             }
7442            }
7443            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7444            second = FALSE;
7445         }
7446         // ignore clicks on holdings
7447         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7448     }
7449
7450     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7451         DragPieceEnd(xPix, yPix); dragging = 0;
7452         if(clearFlag) {
7453             // a deferred attempt to click-click move an empty square on top of a piece
7454             boards[currentMove][y][x] = EmptySquare;
7455             ClearHighlights();
7456             DrawPosition(FALSE, boards[currentMove]);
7457             fromX = fromY = -1; clearFlag = 0;
7458             return;
7459         }
7460         if (appData.animateDragging) {
7461             /* Undo animation damage if any */
7462             DrawPosition(FALSE, NULL);
7463         }
7464         if (second || sweepSelecting) {
7465             /* Second up/down in same square; just abort move */
7466             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7467             second = sweepSelecting = 0;
7468             fromX = fromY = -1;
7469             gatingPiece = EmptySquare;
7470             MarkTargetSquares(1);
7471             ClearHighlights();
7472             gotPremove = 0;
7473             ClearPremoveHighlights();
7474         } else {
7475             /* First upclick in same square; start click-click mode */
7476             SetHighlights(x, y, -1, -1);
7477         }
7478         return;
7479     }
7480
7481     clearFlag = 0;
7482
7483     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7484         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7485         DisplayMessage(_("only marked squares are legal"),"");
7486         DrawPosition(TRUE, NULL);
7487         return; // ignore to-click
7488     }
7489
7490     /* we now have a different from- and (possibly off-board) to-square */
7491     /* Completed move */
7492     if(!sweepSelecting) {
7493         toX = x;
7494         toY = y;
7495     }
7496
7497     piece = boards[currentMove][fromY][fromX];
7498
7499     saveAnimate = appData.animate;
7500     if (clickType == Press) {
7501         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7502         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7503             // must be Edit Position mode with empty-square selected
7504             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7505             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7506             return;
7507         }
7508         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7509             return;
7510         }
7511         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7512             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7513         } else
7514         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7515         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7516           if(appData.sweepSelect) {
7517             promoSweep = defaultPromoChoice;
7518             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7519             selectFlag = 0; lastX = xPix; lastY = yPix;
7520             Sweep(0); // Pawn that is going to promote: preview promotion piece
7521             sweepSelecting = 1;
7522             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7523             MarkTargetSquares(1);
7524           }
7525           return; // promo popup appears on up-click
7526         }
7527         /* Finish clickclick move */
7528         if (appData.animate || appData.highlightLastMove) {
7529             SetHighlights(fromX, fromY, toX, toY);
7530         } else {
7531             ClearHighlights();
7532         }
7533     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7534         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7535         if (appData.animate || appData.highlightLastMove) {
7536             SetHighlights(fromX, fromY, toX, toY);
7537         } else {
7538             ClearHighlights();
7539         }
7540     } else {
7541 #if 0
7542 // [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
7543         /* Finish drag move */
7544         if (appData.highlightLastMove) {
7545             SetHighlights(fromX, fromY, toX, toY);
7546         } else {
7547             ClearHighlights();
7548         }
7549 #endif
7550         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7551         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7552           dragging *= 2;            // flag button-less dragging if we are dragging
7553           MarkTargetSquares(1);
7554           if(x == killX && y == killY) killX = killY = -1; else {
7555             killX = x; killY = y;     //remeber this square as intermediate
7556             ReportClick("put", x, y); // and inform engine
7557             ReportClick("lift", x, y);
7558             MarkTargetSquares(0);
7559             return;
7560           }
7561         }
7562         DragPieceEnd(xPix, yPix); dragging = 0;
7563         /* Don't animate move and drag both */
7564         appData.animate = FALSE;
7565     }
7566
7567     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7568     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7569         ChessSquare piece = boards[currentMove][fromY][fromX];
7570         if(gameMode == EditPosition && piece != EmptySquare &&
7571            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7572             int n;
7573
7574             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7575                 n = PieceToNumber(piece - (int)BlackPawn);
7576                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7577                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7578                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7579             } else
7580             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7581                 n = PieceToNumber(piece);
7582                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7583                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7584                 boards[currentMove][n][BOARD_WIDTH-2]++;
7585             }
7586             boards[currentMove][fromY][fromX] = EmptySquare;
7587         }
7588         ClearHighlights();
7589         fromX = fromY = -1;
7590         MarkTargetSquares(1);
7591         DrawPosition(TRUE, boards[currentMove]);
7592         return;
7593     }
7594
7595     // off-board moves should not be highlighted
7596     if(x < 0 || y < 0) ClearHighlights();
7597     else ReportClick("put", x, y);
7598
7599     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7600
7601     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7602         SetHighlights(fromX, fromY, toX, toY);
7603         MarkTargetSquares(1);
7604         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7605             // [HGM] super: promotion to captured piece selected from holdings
7606             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7607             promotionChoice = TRUE;
7608             // kludge follows to temporarily execute move on display, without promoting yet
7609             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7610             boards[currentMove][toY][toX] = p;
7611             DrawPosition(FALSE, boards[currentMove]);
7612             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7613             boards[currentMove][toY][toX] = q;
7614             DisplayMessage("Click in holdings to choose piece", "");
7615             return;
7616         }
7617         PromotionPopUp(promoChoice);
7618     } else {
7619         int oldMove = currentMove;
7620         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7621         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7622         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7623         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7624            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7625             DrawPosition(TRUE, boards[currentMove]);
7626         MarkTargetSquares(1);
7627         fromX = fromY = -1;
7628     }
7629     appData.animate = saveAnimate;
7630     if (appData.animate || appData.animateDragging) {
7631         /* Undo animation damage if needed */
7632         DrawPosition(FALSE, NULL);
7633     }
7634 }
7635
7636 int
7637 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7638 {   // front-end-free part taken out of PieceMenuPopup
7639     int whichMenu; int xSqr, ySqr;
7640
7641     if(seekGraphUp) { // [HGM] seekgraph
7642         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7643         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7644         return -2;
7645     }
7646
7647     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7648          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7649         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7650         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7651         if(action == Press)   {
7652             originalFlip = flipView;
7653             flipView = !flipView; // temporarily flip board to see game from partners perspective
7654             DrawPosition(TRUE, partnerBoard);
7655             DisplayMessage(partnerStatus, "");
7656             partnerUp = TRUE;
7657         } else if(action == Release) {
7658             flipView = originalFlip;
7659             DrawPosition(TRUE, boards[currentMove]);
7660             partnerUp = FALSE;
7661         }
7662         return -2;
7663     }
7664
7665     xSqr = EventToSquare(x, BOARD_WIDTH);
7666     ySqr = EventToSquare(y, BOARD_HEIGHT);
7667     if (action == Release) {
7668         if(pieceSweep != EmptySquare) {
7669             EditPositionMenuEvent(pieceSweep, toX, toY);
7670             pieceSweep = EmptySquare;
7671         } else UnLoadPV(); // [HGM] pv
7672     }
7673     if (action != Press) return -2; // return code to be ignored
7674     switch (gameMode) {
7675       case IcsExamining:
7676         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7677       case EditPosition:
7678         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7679         if (xSqr < 0 || ySqr < 0) return -1;
7680         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7681         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7682         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7683         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7684         NextPiece(0);
7685         return 2; // grab
7686       case IcsObserving:
7687         if(!appData.icsEngineAnalyze) return -1;
7688       case IcsPlayingWhite:
7689       case IcsPlayingBlack:
7690         if(!appData.zippyPlay) goto noZip;
7691       case AnalyzeMode:
7692       case AnalyzeFile:
7693       case MachinePlaysWhite:
7694       case MachinePlaysBlack:
7695       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7696         if (!appData.dropMenu) {
7697           LoadPV(x, y);
7698           return 2; // flag front-end to grab mouse events
7699         }
7700         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7701            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7702       case EditGame:
7703       noZip:
7704         if (xSqr < 0 || ySqr < 0) return -1;
7705         if (!appData.dropMenu || appData.testLegality &&
7706             gameInfo.variant != VariantBughouse &&
7707             gameInfo.variant != VariantCrazyhouse) return -1;
7708         whichMenu = 1; // drop menu
7709         break;
7710       default:
7711         return -1;
7712     }
7713
7714     if (((*fromX = xSqr) < 0) ||
7715         ((*fromY = ySqr) < 0)) {
7716         *fromX = *fromY = -1;
7717         return -1;
7718     }
7719     if (flipView)
7720       *fromX = BOARD_WIDTH - 1 - *fromX;
7721     else
7722       *fromY = BOARD_HEIGHT - 1 - *fromY;
7723
7724     return whichMenu;
7725 }
7726
7727 void
7728 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7729 {
7730 //    char * hint = lastHint;
7731     FrontEndProgramStats stats;
7732
7733     stats.which = cps == &first ? 0 : 1;
7734     stats.depth = cpstats->depth;
7735     stats.nodes = cpstats->nodes;
7736     stats.score = cpstats->score;
7737     stats.time = cpstats->time;
7738     stats.pv = cpstats->movelist;
7739     stats.hint = lastHint;
7740     stats.an_move_index = 0;
7741     stats.an_move_count = 0;
7742
7743     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7744         stats.hint = cpstats->move_name;
7745         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7746         stats.an_move_count = cpstats->nr_moves;
7747     }
7748
7749     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
7750
7751     SetProgramStats( &stats );
7752 }
7753
7754 void
7755 ClearEngineOutputPane (int which)
7756 {
7757     static FrontEndProgramStats dummyStats;
7758     dummyStats.which = which;
7759     dummyStats.pv = "#";
7760     SetProgramStats( &dummyStats );
7761 }
7762
7763 #define MAXPLAYERS 500
7764
7765 char *
7766 TourneyStandings (int display)
7767 {
7768     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7769     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7770     char result, *p, *names[MAXPLAYERS];
7771
7772     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7773         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7774     names[0] = p = strdup(appData.participants);
7775     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7776
7777     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7778
7779     while(result = appData.results[nr]) {
7780         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7781         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7782         wScore = bScore = 0;
7783         switch(result) {
7784           case '+': wScore = 2; break;
7785           case '-': bScore = 2; break;
7786           case '=': wScore = bScore = 1; break;
7787           case ' ':
7788           case '*': return strdup("busy"); // tourney not finished
7789         }
7790         score[w] += wScore;
7791         score[b] += bScore;
7792         games[w]++;
7793         games[b]++;
7794         nr++;
7795     }
7796     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7797     for(w=0; w<nPlayers; w++) {
7798         bScore = -1;
7799         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7800         ranking[w] = b; points[w] = bScore; score[b] = -2;
7801     }
7802     p = malloc(nPlayers*34+1);
7803     for(w=0; w<nPlayers && w<display; w++)
7804         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7805     free(names[0]);
7806     return p;
7807 }
7808
7809 void
7810 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7811 {       // count all piece types
7812         int p, f, r;
7813         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7814         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7815         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7816                 p = board[r][f];
7817                 pCnt[p]++;
7818                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7819                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7820                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7821                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7822                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7823                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7824         }
7825 }
7826
7827 int
7828 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7829 {
7830         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7831         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7832
7833         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7834         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7835         if(myPawns == 2 && nMine == 3) // KPP
7836             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7837         if(myPawns == 1 && nMine == 2) // KP
7838             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7839         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7840             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7841         if(myPawns) return FALSE;
7842         if(pCnt[WhiteRook+side])
7843             return pCnt[BlackRook-side] ||
7844                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7845                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7846                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7847         if(pCnt[WhiteCannon+side]) {
7848             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7849             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7850         }
7851         if(pCnt[WhiteKnight+side])
7852             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7853         return FALSE;
7854 }
7855
7856 int
7857 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7858 {
7859         VariantClass v = gameInfo.variant;
7860
7861         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7862         if(v == VariantShatranj) return TRUE; // always winnable through baring
7863         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7864         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7865
7866         if(v == VariantXiangqi) {
7867                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7868
7869                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7870                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7871                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7872                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7873                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7874                 if(stale) // we have at least one last-rank P plus perhaps C
7875                     return majors // KPKX
7876                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7877                 else // KCA*E*
7878                     return pCnt[WhiteFerz+side] // KCAK
7879                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7880                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7881                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7882
7883         } else if(v == VariantKnightmate) {
7884                 if(nMine == 1) return FALSE;
7885                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7886         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7887                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7888
7889                 if(nMine == 1) return FALSE; // bare King
7890                 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
7891                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7892                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7893                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7894                 if(pCnt[WhiteKnight+side])
7895                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7896                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7897                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7898                 if(nBishops)
7899                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7900                 if(pCnt[WhiteAlfil+side])
7901                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7902                 if(pCnt[WhiteWazir+side])
7903                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7904         }
7905
7906         return TRUE;
7907 }
7908
7909 int
7910 CompareWithRights (Board b1, Board b2)
7911 {
7912     int rights = 0;
7913     if(!CompareBoards(b1, b2)) return FALSE;
7914     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7915     /* compare castling rights */
7916     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7917            rights++; /* King lost rights, while rook still had them */
7918     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7919         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7920            rights++; /* but at least one rook lost them */
7921     }
7922     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7923            rights++;
7924     if( b1[CASTLING][5] != NoRights ) {
7925         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7926            rights++;
7927     }
7928     return rights == 0;
7929 }
7930
7931 int
7932 Adjudicate (ChessProgramState *cps)
7933 {       // [HGM] some adjudications useful with buggy engines
7934         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7935         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7936         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7937         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7938         int k, drop, count = 0; static int bare = 1;
7939         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7940         Boolean canAdjudicate = !appData.icsActive;
7941
7942         // most tests only when we understand the game, i.e. legality-checking on
7943             if( appData.testLegality )
7944             {   /* [HGM] Some more adjudications for obstinate engines */
7945                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7946                 static int moveCount = 6;
7947                 ChessMove result;
7948                 char *reason = NULL;
7949
7950                 /* Count what is on board. */
7951                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7952
7953                 /* Some material-based adjudications that have to be made before stalemate test */
7954                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7955                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7956                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7957                      if(canAdjudicate && appData.checkMates) {
7958                          if(engineOpponent)
7959                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7960                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7961                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7962                          return 1;
7963                      }
7964                 }
7965
7966                 /* Bare King in Shatranj (loses) or Losers (wins) */
7967                 if( nrW == 1 || nrB == 1) {
7968                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7969                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7970                      if(canAdjudicate && appData.checkMates) {
7971                          if(engineOpponent)
7972                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7973                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7974                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7975                          return 1;
7976                      }
7977                   } else
7978                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7979                   {    /* bare King */
7980                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7981                         if(canAdjudicate && appData.checkMates) {
7982                             /* but only adjudicate if adjudication enabled */
7983                             if(engineOpponent)
7984                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7985                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7986                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7987                             return 1;
7988                         }
7989                   }
7990                 } else bare = 1;
7991
7992
7993             // don't wait for engine to announce game end if we can judge ourselves
7994             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7995               case MT_CHECK:
7996                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7997                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7998                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7999                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8000                             checkCnt++;
8001                         if(checkCnt >= 2) {
8002                             reason = "Xboard adjudication: 3rd check";
8003                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8004                             break;
8005                         }
8006                     }
8007                 }
8008               case MT_NONE:
8009               default:
8010                 break;
8011               case MT_STALEMATE:
8012               case MT_STAINMATE:
8013                 reason = "Xboard adjudication: Stalemate";
8014                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8015                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8016                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8017                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8018                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8019                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8020                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8021                                                                         EP_CHECKMATE : EP_WINS);
8022                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8023                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8024                 }
8025                 break;
8026               case MT_CHECKMATE:
8027                 reason = "Xboard adjudication: Checkmate";
8028                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8029                 if(gameInfo.variant == VariantShogi) {
8030                     if(forwardMostMove > backwardMostMove
8031                        && moveList[forwardMostMove-1][1] == '@'
8032                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8033                         reason = "XBoard adjudication: pawn-drop mate";
8034                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8035                     }
8036                 }
8037                 break;
8038             }
8039
8040                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8041                     case EP_STALEMATE:
8042                         result = GameIsDrawn; break;
8043                     case EP_CHECKMATE:
8044                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8045                     case EP_WINS:
8046                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8047                     default:
8048                         result = EndOfFile;
8049                 }
8050                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8051                     if(engineOpponent)
8052                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8053                     GameEnds( result, reason, GE_XBOARD );
8054                     return 1;
8055                 }
8056
8057                 /* Next absolutely insufficient mating material. */
8058                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8059                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8060                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8061
8062                      /* always flag draws, for judging claims */
8063                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8064
8065                      if(canAdjudicate && appData.materialDraws) {
8066                          /* but only adjudicate them if adjudication enabled */
8067                          if(engineOpponent) {
8068                            SendToProgram("force\n", engineOpponent); // suppress reply
8069                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8070                          }
8071                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8072                          return 1;
8073                      }
8074                 }
8075
8076                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8077                 if(gameInfo.variant == VariantXiangqi ?
8078                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8079                  : nrW + nrB == 4 &&
8080                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8081                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8082                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8083                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8084                    ) ) {
8085                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8086                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8087                           if(engineOpponent) {
8088                             SendToProgram("force\n", engineOpponent); // suppress reply
8089                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8090                           }
8091                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8092                           return 1;
8093                      }
8094                 } else moveCount = 6;
8095             }
8096
8097         // Repetition draws and 50-move rule can be applied independently of legality testing
8098
8099                 /* Check for rep-draws */
8100                 count = 0;
8101                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8102                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8103                 for(k = forwardMostMove-2;
8104                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8105                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8106                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8107                     k-=2)
8108                 {   int rights=0;
8109                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8110                         /* compare castling rights */
8111                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8112                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8113                                 rights++; /* King lost rights, while rook still had them */
8114                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8115                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8116                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8117                                    rights++; /* but at least one rook lost them */
8118                         }
8119                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8120                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8121                                 rights++;
8122                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8123                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8124                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8125                                    rights++;
8126                         }
8127                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8128                             && appData.drawRepeats > 1) {
8129                              /* adjudicate after user-specified nr of repeats */
8130                              int result = GameIsDrawn;
8131                              char *details = "XBoard adjudication: repetition draw";
8132                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8133                                 // [HGM] xiangqi: check for forbidden perpetuals
8134                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8135                                 for(m=forwardMostMove; m>k; m-=2) {
8136                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8137                                         ourPerpetual = 0; // the current mover did not always check
8138                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8139                                         hisPerpetual = 0; // the opponent did not always check
8140                                 }
8141                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8142                                                                         ourPerpetual, hisPerpetual);
8143                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8144                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8145                                     details = "Xboard adjudication: perpetual checking";
8146                                 } else
8147                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8148                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8149                                 } else
8150                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8151                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8152                                         result = BlackWins;
8153                                         details = "Xboard adjudication: repetition";
8154                                     }
8155                                 } else // it must be XQ
8156                                 // Now check for perpetual chases
8157                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8158                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8159                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8160                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8161                                         static char resdet[MSG_SIZ];
8162                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8163                                         details = resdet;
8164                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8165                                     } else
8166                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8167                                         break; // Abort repetition-checking loop.
8168                                 }
8169                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8170                              }
8171                              if(engineOpponent) {
8172                                SendToProgram("force\n", engineOpponent); // suppress reply
8173                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8174                              }
8175                              GameEnds( result, details, GE_XBOARD );
8176                              return 1;
8177                         }
8178                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8179                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8180                     }
8181                 }
8182
8183                 /* Now we test for 50-move draws. Determine ply count */
8184                 count = forwardMostMove;
8185                 /* look for last irreversble move */
8186                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8187                     count--;
8188                 /* if we hit starting position, add initial plies */
8189                 if( count == backwardMostMove )
8190                     count -= initialRulePlies;
8191                 count = forwardMostMove - count;
8192                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8193                         // adjust reversible move counter for checks in Xiangqi
8194                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8195                         if(i < backwardMostMove) i = backwardMostMove;
8196                         while(i <= forwardMostMove) {
8197                                 lastCheck = inCheck; // check evasion does not count
8198                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8199                                 if(inCheck || lastCheck) count--; // check does not count
8200                                 i++;
8201                         }
8202                 }
8203                 if( count >= 100)
8204                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8205                          /* this is used to judge if draw claims are legal */
8206                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8207                          if(engineOpponent) {
8208                            SendToProgram("force\n", engineOpponent); // suppress reply
8209                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8210                          }
8211                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8212                          return 1;
8213                 }
8214
8215                 /* if draw offer is pending, treat it as a draw claim
8216                  * when draw condition present, to allow engines a way to
8217                  * claim draws before making their move to avoid a race
8218                  * condition occurring after their move
8219                  */
8220                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8221                          char *p = NULL;
8222                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8223                              p = "Draw claim: 50-move rule";
8224                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8225                              p = "Draw claim: 3-fold repetition";
8226                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8227                              p = "Draw claim: insufficient mating material";
8228                          if( p != NULL && canAdjudicate) {
8229                              if(engineOpponent) {
8230                                SendToProgram("force\n", engineOpponent); // suppress reply
8231                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8232                              }
8233                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8234                              return 1;
8235                          }
8236                 }
8237
8238                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8239                     if(engineOpponent) {
8240                       SendToProgram("force\n", engineOpponent); // suppress reply
8241                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8242                     }
8243                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8244                     return 1;
8245                 }
8246         return 0;
8247 }
8248
8249 char *
8250 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8251 {   // [HGM] book: this routine intercepts moves to simulate book replies
8252     char *bookHit = NULL;
8253
8254     //first determine if the incoming move brings opponent into his book
8255     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8256         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8257     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8258     if(bookHit != NULL && !cps->bookSuspend) {
8259         // make sure opponent is not going to reply after receiving move to book position
8260         SendToProgram("force\n", cps);
8261         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8262     }
8263     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8264     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8265     // now arrange restart after book miss
8266     if(bookHit) {
8267         // after a book hit we never send 'go', and the code after the call to this routine
8268         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8269         char buf[MSG_SIZ], *move = bookHit;
8270         if(cps->useSAN) {
8271             int fromX, fromY, toX, toY;
8272             char promoChar;
8273             ChessMove moveType;
8274             move = buf + 30;
8275             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8276                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8277                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8278                                     PosFlags(forwardMostMove),
8279                                     fromY, fromX, toY, toX, promoChar, move);
8280             } else {
8281                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8282                 bookHit = NULL;
8283             }
8284         }
8285         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8286         SendToProgram(buf, cps);
8287         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8288     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8289         SendToProgram("go\n", cps);
8290         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8291     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8292         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8293             SendToProgram("go\n", cps);
8294         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8295     }
8296     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8297 }
8298
8299 int
8300 LoadError (char *errmess, ChessProgramState *cps)
8301 {   // unloads engine and switches back to -ncp mode if it was first
8302     if(cps->initDone) return FALSE;
8303     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8304     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8305     cps->pr = NoProc;
8306     if(cps == &first) {
8307         appData.noChessProgram = TRUE;
8308         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8309         gameMode = BeginningOfGame; ModeHighlight();
8310         SetNCPMode();
8311     }
8312     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8313     DisplayMessage("", ""); // erase waiting message
8314     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8315     return TRUE;
8316 }
8317
8318 char *savedMessage;
8319 ChessProgramState *savedState;
8320 void
8321 DeferredBookMove (void)
8322 {
8323         if(savedState->lastPing != savedState->lastPong)
8324                     ScheduleDelayedEvent(DeferredBookMove, 10);
8325         else
8326         HandleMachineMove(savedMessage, savedState);
8327 }
8328
8329 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8330 static ChessProgramState *stalledEngine;
8331 static char stashedInputMove[MSG_SIZ];
8332
8333 void
8334 HandleMachineMove (char *message, ChessProgramState *cps)
8335 {
8336     static char firstLeg[20];
8337     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8338     char realname[MSG_SIZ];
8339     int fromX, fromY, toX, toY;
8340     ChessMove moveType;
8341     char promoChar, roar;
8342     char *p, *pv=buf1;
8343     int machineWhite, oldError;
8344     char *bookHit;
8345
8346     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8347         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8348         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8349             DisplayError(_("Invalid pairing from pairing engine"), 0);
8350             return;
8351         }
8352         pairingReceived = 1;
8353         NextMatchGame();
8354         return; // Skim the pairing messages here.
8355     }
8356
8357     oldError = cps->userError; cps->userError = 0;
8358
8359 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8360     /*
8361      * Kludge to ignore BEL characters
8362      */
8363     while (*message == '\007') message++;
8364
8365     /*
8366      * [HGM] engine debug message: ignore lines starting with '#' character
8367      */
8368     if(cps->debug && *message == '#') return;
8369
8370     /*
8371      * Look for book output
8372      */
8373     if (cps == &first && bookRequested) {
8374         if (message[0] == '\t' || message[0] == ' ') {
8375             /* Part of the book output is here; append it */
8376             strcat(bookOutput, message);
8377             strcat(bookOutput, "  \n");
8378             return;
8379         } else if (bookOutput[0] != NULLCHAR) {
8380             /* All of book output has arrived; display it */
8381             char *p = bookOutput;
8382             while (*p != NULLCHAR) {
8383                 if (*p == '\t') *p = ' ';
8384                 p++;
8385             }
8386             DisplayInformation(bookOutput);
8387             bookRequested = FALSE;
8388             /* Fall through to parse the current output */
8389         }
8390     }
8391
8392     /*
8393      * Look for machine move.
8394      */
8395     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8396         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8397     {
8398         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8399             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8400             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8401             stalledEngine = cps;
8402             if(appData.ponderNextMove) { // bring opponent out of ponder
8403                 if(gameMode == TwoMachinesPlay) {
8404                     if(cps->other->pause)
8405                         PauseEngine(cps->other);
8406                     else
8407                         SendToProgram("easy\n", cps->other);
8408                 }
8409             }
8410             StopClocks();
8411             return;
8412         }
8413
8414         /* This method is only useful on engines that support ping */
8415         if (cps->lastPing != cps->lastPong) {
8416           if (gameMode == BeginningOfGame) {
8417             /* Extra move from before last new; ignore */
8418             if (appData.debugMode) {
8419                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8420             }
8421           } else {
8422             if (appData.debugMode) {
8423                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8424                         cps->which, gameMode);
8425             }
8426
8427             SendToProgram("undo\n", cps);
8428           }
8429           return;
8430         }
8431
8432         switch (gameMode) {
8433           case BeginningOfGame:
8434             /* Extra move from before last reset; ignore */
8435             if (appData.debugMode) {
8436                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8437             }
8438             return;
8439
8440           case EndOfGame:
8441           case IcsIdle:
8442           default:
8443             /* Extra move after we tried to stop.  The mode test is
8444                not a reliable way of detecting this problem, but it's
8445                the best we can do on engines that don't support ping.
8446             */
8447             if (appData.debugMode) {
8448                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8449                         cps->which, gameMode);
8450             }
8451             SendToProgram("undo\n", cps);
8452             return;
8453
8454           case MachinePlaysWhite:
8455           case IcsPlayingWhite:
8456             machineWhite = TRUE;
8457             break;
8458
8459           case MachinePlaysBlack:
8460           case IcsPlayingBlack:
8461             machineWhite = FALSE;
8462             break;
8463
8464           case TwoMachinesPlay:
8465             machineWhite = (cps->twoMachinesColor[0] == 'w');
8466             break;
8467         }
8468         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8469             if (appData.debugMode) {
8470                 fprintf(debugFP,
8471                         "Ignoring move out of turn by %s, gameMode %d"
8472                         ", forwardMost %d\n",
8473                         cps->which, gameMode, forwardMostMove);
8474             }
8475             return;
8476         }
8477
8478         if(cps->alphaRank) AlphaRank(machineMove, 4);
8479
8480         // [HGM] lion: (some very limited) support for Alien protocol
8481         killX = killY = -1;
8482         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8483             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8484             return;
8485         } else if(firstLeg[0]) { // there was a previous leg;
8486             // only support case where same piece makes two step (and don't even test that!)
8487             char buf[20], *p = machineMove+1, *q = buf+1, f;
8488             safeStrCpy(buf, machineMove, 20);
8489             while(isdigit(*q)) q++; // find start of to-square
8490             safeStrCpy(machineMove, firstLeg, 20);
8491             while(isdigit(*p)) p++;
8492             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8493             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8494             firstLeg[0] = NULLCHAR;
8495         }
8496
8497         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8498                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8499             /* Machine move could not be parsed; ignore it. */
8500           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8501                     machineMove, _(cps->which));
8502             DisplayMoveError(buf1);
8503             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8504                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8505             if (gameMode == TwoMachinesPlay) {
8506               GameEnds(machineWhite ? BlackWins : WhiteWins,
8507                        buf1, GE_XBOARD);
8508             }
8509             return;
8510         }
8511
8512         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8513         /* So we have to redo legality test with true e.p. status here,  */
8514         /* to make sure an illegal e.p. capture does not slip through,   */
8515         /* to cause a forfeit on a justified illegal-move complaint      */
8516         /* of the opponent.                                              */
8517         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8518            ChessMove moveType;
8519            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8520                              fromY, fromX, toY, toX, promoChar);
8521             if(moveType == IllegalMove) {
8522               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8523                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8524                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8525                            buf1, GE_XBOARD);
8526                 return;
8527            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8528            /* [HGM] Kludge to handle engines that send FRC-style castling
8529               when they shouldn't (like TSCP-Gothic) */
8530            switch(moveType) {
8531              case WhiteASideCastleFR:
8532              case BlackASideCastleFR:
8533                toX+=2;
8534                currentMoveString[2]++;
8535                break;
8536              case WhiteHSideCastleFR:
8537              case BlackHSideCastleFR:
8538                toX--;
8539                currentMoveString[2]--;
8540                break;
8541              default: ; // nothing to do, but suppresses warning of pedantic compilers
8542            }
8543         }
8544         hintRequested = FALSE;
8545         lastHint[0] = NULLCHAR;
8546         bookRequested = FALSE;
8547         /* Program may be pondering now */
8548         cps->maybeThinking = TRUE;
8549         if (cps->sendTime == 2) cps->sendTime = 1;
8550         if (cps->offeredDraw) cps->offeredDraw--;
8551
8552         /* [AS] Save move info*/
8553         pvInfoList[ forwardMostMove ].score = programStats.score;
8554         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8555         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8556
8557         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8558
8559         /* Test suites abort the 'game' after one move */
8560         if(*appData.finger) {
8561            static FILE *f;
8562            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8563            if(!f) f = fopen(appData.finger, "w");
8564            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8565            else { DisplayFatalError("Bad output file", errno, 0); return; }
8566            free(fen);
8567            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8568         }
8569
8570         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8571         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8572             int count = 0;
8573
8574             while( count < adjudicateLossPlies ) {
8575                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8576
8577                 if( count & 1 ) {
8578                     score = -score; /* Flip score for winning side */
8579                 }
8580
8581                 if( score > adjudicateLossThreshold ) {
8582                     break;
8583                 }
8584
8585                 count++;
8586             }
8587
8588             if( count >= adjudicateLossPlies ) {
8589                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8590
8591                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8592                     "Xboard adjudication",
8593                     GE_XBOARD );
8594
8595                 return;
8596             }
8597         }
8598
8599         if(Adjudicate(cps)) {
8600             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8601             return; // [HGM] adjudicate: for all automatic game ends
8602         }
8603
8604 #if ZIPPY
8605         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8606             first.initDone) {
8607           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8608                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8609                 SendToICS("draw ");
8610                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8611           }
8612           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8613           ics_user_moved = 1;
8614           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8615                 char buf[3*MSG_SIZ];
8616
8617                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8618                         programStats.score / 100.,
8619                         programStats.depth,
8620                         programStats.time / 100.,
8621                         (unsigned int)programStats.nodes,
8622                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8623                         programStats.movelist);
8624                 SendToICS(buf);
8625 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8626           }
8627         }
8628 #endif
8629
8630         /* [AS] Clear stats for next move */
8631         ClearProgramStats();
8632         thinkOutput[0] = NULLCHAR;
8633         hiddenThinkOutputState = 0;
8634
8635         bookHit = NULL;
8636         if (gameMode == TwoMachinesPlay) {
8637             /* [HGM] relaying draw offers moved to after reception of move */
8638             /* and interpreting offer as claim if it brings draw condition */
8639             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8640                 SendToProgram("draw\n", cps->other);
8641             }
8642             if (cps->other->sendTime) {
8643                 SendTimeRemaining(cps->other,
8644                                   cps->other->twoMachinesColor[0] == 'w');
8645             }
8646             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8647             if (firstMove && !bookHit) {
8648                 firstMove = FALSE;
8649                 if (cps->other->useColors) {
8650                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8651                 }
8652                 SendToProgram("go\n", cps->other);
8653             }
8654             cps->other->maybeThinking = TRUE;
8655         }
8656
8657         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8658
8659         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8660
8661         if (!pausing && appData.ringBellAfterMoves) {
8662             if(!roar) RingBell();
8663         }
8664
8665         /*
8666          * Reenable menu items that were disabled while
8667          * machine was thinking
8668          */
8669         if (gameMode != TwoMachinesPlay)
8670             SetUserThinkingEnables();
8671
8672         // [HGM] book: after book hit opponent has received move and is now in force mode
8673         // force the book reply into it, and then fake that it outputted this move by jumping
8674         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8675         if(bookHit) {
8676                 static char bookMove[MSG_SIZ]; // a bit generous?
8677
8678                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8679                 strcat(bookMove, bookHit);
8680                 message = bookMove;
8681                 cps = cps->other;
8682                 programStats.nodes = programStats.depth = programStats.time =
8683                 programStats.score = programStats.got_only_move = 0;
8684                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8685
8686                 if(cps->lastPing != cps->lastPong) {
8687                     savedMessage = message; // args for deferred call
8688                     savedState = cps;
8689                     ScheduleDelayedEvent(DeferredBookMove, 10);
8690                     return;
8691                 }
8692                 goto FakeBookMove;
8693         }
8694
8695         return;
8696     }
8697
8698     /* Set special modes for chess engines.  Later something general
8699      *  could be added here; for now there is just one kludge feature,
8700      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8701      *  when "xboard" is given as an interactive command.
8702      */
8703     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8704         cps->useSigint = FALSE;
8705         cps->useSigterm = FALSE;
8706     }
8707     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8708       ParseFeatures(message+8, cps);
8709       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8710     }
8711
8712     if (!strncmp(message, "setup ", 6) && 
8713         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8714           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8715                                         ) { // [HGM] allow first engine to define opening position
8716       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8717       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8718       *buf = NULLCHAR;
8719       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8720       if(startedFromSetupPosition) return;
8721       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8722       if(dummy >= 3) {
8723         while(message[s] && message[s++] != ' ');
8724         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8725            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8726             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8727             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8728           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8729           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8730         }
8731       }
8732       ParseFEN(boards[0], &dummy, message+s, FALSE);
8733       DrawPosition(TRUE, boards[0]);
8734       startedFromSetupPosition = TRUE;
8735       return;
8736     }
8737     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8738      * want this, I was asked to put it in, and obliged.
8739      */
8740     if (!strncmp(message, "setboard ", 9)) {
8741         Board initial_position;
8742
8743         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8744
8745         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8746             DisplayError(_("Bad FEN received from engine"), 0);
8747             return ;
8748         } else {
8749            Reset(TRUE, FALSE);
8750            CopyBoard(boards[0], initial_position);
8751            initialRulePlies = FENrulePlies;
8752            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8753            else gameMode = MachinePlaysBlack;
8754            DrawPosition(FALSE, boards[currentMove]);
8755         }
8756         return;
8757     }
8758
8759     /*
8760      * Look for communication commands
8761      */
8762     if (!strncmp(message, "telluser ", 9)) {
8763         if(message[9] == '\\' && message[10] == '\\')
8764             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8765         PlayTellSound();
8766         DisplayNote(message + 9);
8767         return;
8768     }
8769     if (!strncmp(message, "tellusererror ", 14)) {
8770         cps->userError = 1;
8771         if(message[14] == '\\' && message[15] == '\\')
8772             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8773         PlayTellSound();
8774         DisplayError(message + 14, 0);
8775         return;
8776     }
8777     if (!strncmp(message, "tellopponent ", 13)) {
8778       if (appData.icsActive) {
8779         if (loggedOn) {
8780           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8781           SendToICS(buf1);
8782         }
8783       } else {
8784         DisplayNote(message + 13);
8785       }
8786       return;
8787     }
8788     if (!strncmp(message, "tellothers ", 11)) {
8789       if (appData.icsActive) {
8790         if (loggedOn) {
8791           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8792           SendToICS(buf1);
8793         }
8794       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8795       return;
8796     }
8797     if (!strncmp(message, "tellall ", 8)) {
8798       if (appData.icsActive) {
8799         if (loggedOn) {
8800           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8801           SendToICS(buf1);
8802         }
8803       } else {
8804         DisplayNote(message + 8);
8805       }
8806       return;
8807     }
8808     if (strncmp(message, "warning", 7) == 0) {
8809         /* Undocumented feature, use tellusererror in new code */
8810         DisplayError(message, 0);
8811         return;
8812     }
8813     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8814         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8815         strcat(realname, " query");
8816         AskQuestion(realname, buf2, buf1, cps->pr);
8817         return;
8818     }
8819     /* Commands from the engine directly to ICS.  We don't allow these to be
8820      *  sent until we are logged on. Crafty kibitzes have been known to
8821      *  interfere with the login process.
8822      */
8823     if (loggedOn) {
8824         if (!strncmp(message, "tellics ", 8)) {
8825             SendToICS(message + 8);
8826             SendToICS("\n");
8827             return;
8828         }
8829         if (!strncmp(message, "tellicsnoalias ", 15)) {
8830             SendToICS(ics_prefix);
8831             SendToICS(message + 15);
8832             SendToICS("\n");
8833             return;
8834         }
8835         /* The following are for backward compatibility only */
8836         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8837             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8838             SendToICS(ics_prefix);
8839             SendToICS(message);
8840             SendToICS("\n");
8841             return;
8842         }
8843     }
8844     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8845         if(initPing == cps->lastPong) {
8846             if(gameInfo.variant == VariantUnknown) {
8847                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8848                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8849                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8850             }
8851             initPing = -1;
8852         }
8853         return;
8854     }
8855     if(!strncmp(message, "highlight ", 10)) {
8856         if(appData.testLegality && appData.markers) return;
8857         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8858         return;
8859     }
8860     if(!strncmp(message, "click ", 6)) {
8861         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8862         if(appData.testLegality || !appData.oneClick) return;
8863         sscanf(message+6, "%c%d%c", &f, &y, &c);
8864         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8865         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8866         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8867         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8868         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8869         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8870             LeftClick(Release, lastLeftX, lastLeftY);
8871         controlKey  = (c == ',');
8872         LeftClick(Press, x, y);
8873         LeftClick(Release, x, y);
8874         first.highlight = f;
8875         return;
8876     }
8877     /*
8878      * If the move is illegal, cancel it and redraw the board.
8879      * Also deal with other error cases.  Matching is rather loose
8880      * here to accommodate engines written before the spec.
8881      */
8882     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8883         strncmp(message, "Error", 5) == 0) {
8884         if (StrStr(message, "name") ||
8885             StrStr(message, "rating") || StrStr(message, "?") ||
8886             StrStr(message, "result") || StrStr(message, "board") ||
8887             StrStr(message, "bk") || StrStr(message, "computer") ||
8888             StrStr(message, "variant") || StrStr(message, "hint") ||
8889             StrStr(message, "random") || StrStr(message, "depth") ||
8890             StrStr(message, "accepted")) {
8891             return;
8892         }
8893         if (StrStr(message, "protover")) {
8894           /* Program is responding to input, so it's apparently done
8895              initializing, and this error message indicates it is
8896              protocol version 1.  So we don't need to wait any longer
8897              for it to initialize and send feature commands. */
8898           FeatureDone(cps, 1);
8899           cps->protocolVersion = 1;
8900           return;
8901         }
8902         cps->maybeThinking = FALSE;
8903
8904         if (StrStr(message, "draw")) {
8905             /* Program doesn't have "draw" command */
8906             cps->sendDrawOffers = 0;
8907             return;
8908         }
8909         if (cps->sendTime != 1 &&
8910             (StrStr(message, "time") || StrStr(message, "otim"))) {
8911           /* Program apparently doesn't have "time" or "otim" command */
8912           cps->sendTime = 0;
8913           return;
8914         }
8915         if (StrStr(message, "analyze")) {
8916             cps->analysisSupport = FALSE;
8917             cps->analyzing = FALSE;
8918 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8919             EditGameEvent(); // [HGM] try to preserve loaded game
8920             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8921             DisplayError(buf2, 0);
8922             return;
8923         }
8924         if (StrStr(message, "(no matching move)st")) {
8925           /* Special kludge for GNU Chess 4 only */
8926           cps->stKludge = TRUE;
8927           SendTimeControl(cps, movesPerSession, timeControl,
8928                           timeIncrement, appData.searchDepth,
8929                           searchTime);
8930           return;
8931         }
8932         if (StrStr(message, "(no matching move)sd")) {
8933           /* Special kludge for GNU Chess 4 only */
8934           cps->sdKludge = TRUE;
8935           SendTimeControl(cps, movesPerSession, timeControl,
8936                           timeIncrement, appData.searchDepth,
8937                           searchTime);
8938           return;
8939         }
8940         if (!StrStr(message, "llegal")) {
8941             return;
8942         }
8943         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8944             gameMode == IcsIdle) return;
8945         if (forwardMostMove <= backwardMostMove) return;
8946         if (pausing) PauseEvent();
8947       if(appData.forceIllegal) {
8948             // [HGM] illegal: machine refused move; force position after move into it
8949           SendToProgram("force\n", cps);
8950           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8951                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8952                 // when black is to move, while there might be nothing on a2 or black
8953                 // might already have the move. So send the board as if white has the move.
8954                 // But first we must change the stm of the engine, as it refused the last move
8955                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8956                 if(WhiteOnMove(forwardMostMove)) {
8957                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8958                     SendBoard(cps, forwardMostMove); // kludgeless board
8959                 } else {
8960                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8961                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8962                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8963                 }
8964           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8965             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8966                  gameMode == TwoMachinesPlay)
8967               SendToProgram("go\n", cps);
8968             return;
8969       } else
8970         if (gameMode == PlayFromGameFile) {
8971             /* Stop reading this game file */
8972             gameMode = EditGame;
8973             ModeHighlight();
8974         }
8975         /* [HGM] illegal-move claim should forfeit game when Xboard */
8976         /* only passes fully legal moves                            */
8977         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8978             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8979                                 "False illegal-move claim", GE_XBOARD );
8980             return; // do not take back move we tested as valid
8981         }
8982         currentMove = forwardMostMove-1;
8983         DisplayMove(currentMove-1); /* before DisplayMoveError */
8984         SwitchClocks(forwardMostMove-1); // [HGM] race
8985         DisplayBothClocks();
8986         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8987                 parseList[currentMove], _(cps->which));
8988         DisplayMoveError(buf1);
8989         DrawPosition(FALSE, boards[currentMove]);
8990
8991         SetUserThinkingEnables();
8992         return;
8993     }
8994     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8995         /* Program has a broken "time" command that
8996            outputs a string not ending in newline.
8997            Don't use it. */
8998         cps->sendTime = 0;
8999     }
9000
9001     /*
9002      * If chess program startup fails, exit with an error message.
9003      * Attempts to recover here are futile. [HGM] Well, we try anyway
9004      */
9005     if ((StrStr(message, "unknown host") != NULL)
9006         || (StrStr(message, "No remote directory") != NULL)
9007         || (StrStr(message, "not found") != NULL)
9008         || (StrStr(message, "No such file") != NULL)
9009         || (StrStr(message, "can't alloc") != NULL)
9010         || (StrStr(message, "Permission denied") != NULL)) {
9011
9012         cps->maybeThinking = FALSE;
9013         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9014                 _(cps->which), cps->program, cps->host, message);
9015         RemoveInputSource(cps->isr);
9016         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9017             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9018             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9019         }
9020         return;
9021     }
9022
9023     /*
9024      * Look for hint output
9025      */
9026     if (sscanf(message, "Hint: %s", buf1) == 1) {
9027         if (cps == &first && hintRequested) {
9028             hintRequested = FALSE;
9029             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9030                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9031                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9032                                     PosFlags(forwardMostMove),
9033                                     fromY, fromX, toY, toX, promoChar, buf1);
9034                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9035                 DisplayInformation(buf2);
9036             } else {
9037                 /* Hint move could not be parsed!? */
9038               snprintf(buf2, sizeof(buf2),
9039                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9040                         buf1, _(cps->which));
9041                 DisplayError(buf2, 0);
9042             }
9043         } else {
9044           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9045         }
9046         return;
9047     }
9048
9049     /*
9050      * Ignore other messages if game is not in progress
9051      */
9052     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9053         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9054
9055     /*
9056      * look for win, lose, draw, or draw offer
9057      */
9058     if (strncmp(message, "1-0", 3) == 0) {
9059         char *p, *q, *r = "";
9060         p = strchr(message, '{');
9061         if (p) {
9062             q = strchr(p, '}');
9063             if (q) {
9064                 *q = NULLCHAR;
9065                 r = p + 1;
9066             }
9067         }
9068         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9069         return;
9070     } else if (strncmp(message, "0-1", 3) == 0) {
9071         char *p, *q, *r = "";
9072         p = strchr(message, '{');
9073         if (p) {
9074             q = strchr(p, '}');
9075             if (q) {
9076                 *q = NULLCHAR;
9077                 r = p + 1;
9078             }
9079         }
9080         /* Kludge for Arasan 4.1 bug */
9081         if (strcmp(r, "Black resigns") == 0) {
9082             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9083             return;
9084         }
9085         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9086         return;
9087     } else if (strncmp(message, "1/2", 3) == 0) {
9088         char *p, *q, *r = "";
9089         p = strchr(message, '{');
9090         if (p) {
9091             q = strchr(p, '}');
9092             if (q) {
9093                 *q = NULLCHAR;
9094                 r = p + 1;
9095             }
9096         }
9097
9098         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9099         return;
9100
9101     } else if (strncmp(message, "White resign", 12) == 0) {
9102         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9103         return;
9104     } else if (strncmp(message, "Black resign", 12) == 0) {
9105         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9106         return;
9107     } else if (strncmp(message, "White matches", 13) == 0 ||
9108                strncmp(message, "Black matches", 13) == 0   ) {
9109         /* [HGM] ignore GNUShogi noises */
9110         return;
9111     } else if (strncmp(message, "White", 5) == 0 &&
9112                message[5] != '(' &&
9113                StrStr(message, "Black") == NULL) {
9114         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9115         return;
9116     } else if (strncmp(message, "Black", 5) == 0 &&
9117                message[5] != '(') {
9118         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9119         return;
9120     } else if (strcmp(message, "resign") == 0 ||
9121                strcmp(message, "computer resigns") == 0) {
9122         switch (gameMode) {
9123           case MachinePlaysBlack:
9124           case IcsPlayingBlack:
9125             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9126             break;
9127           case MachinePlaysWhite:
9128           case IcsPlayingWhite:
9129             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9130             break;
9131           case TwoMachinesPlay:
9132             if (cps->twoMachinesColor[0] == 'w')
9133               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9134             else
9135               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9136             break;
9137           default:
9138             /* can't happen */
9139             break;
9140         }
9141         return;
9142     } else if (strncmp(message, "opponent mates", 14) == 0) {
9143         switch (gameMode) {
9144           case MachinePlaysBlack:
9145           case IcsPlayingBlack:
9146             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9147             break;
9148           case MachinePlaysWhite:
9149           case IcsPlayingWhite:
9150             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9151             break;
9152           case TwoMachinesPlay:
9153             if (cps->twoMachinesColor[0] == 'w')
9154               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9155             else
9156               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9157             break;
9158           default:
9159             /* can't happen */
9160             break;
9161         }
9162         return;
9163     } else if (strncmp(message, "computer mates", 14) == 0) {
9164         switch (gameMode) {
9165           case MachinePlaysBlack:
9166           case IcsPlayingBlack:
9167             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9168             break;
9169           case MachinePlaysWhite:
9170           case IcsPlayingWhite:
9171             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9172             break;
9173           case TwoMachinesPlay:
9174             if (cps->twoMachinesColor[0] == 'w')
9175               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9176             else
9177               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9178             break;
9179           default:
9180             /* can't happen */
9181             break;
9182         }
9183         return;
9184     } else if (strncmp(message, "checkmate", 9) == 0) {
9185         if (WhiteOnMove(forwardMostMove)) {
9186             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9187         } else {
9188             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9189         }
9190         return;
9191     } else if (strstr(message, "Draw") != NULL ||
9192                strstr(message, "game is a draw") != NULL) {
9193         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9194         return;
9195     } else if (strstr(message, "offer") != NULL &&
9196                strstr(message, "draw") != NULL) {
9197 #if ZIPPY
9198         if (appData.zippyPlay && first.initDone) {
9199             /* Relay offer to ICS */
9200             SendToICS(ics_prefix);
9201             SendToICS("draw\n");
9202         }
9203 #endif
9204         cps->offeredDraw = 2; /* valid until this engine moves twice */
9205         if (gameMode == TwoMachinesPlay) {
9206             if (cps->other->offeredDraw) {
9207                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9208             /* [HGM] in two-machine mode we delay relaying draw offer      */
9209             /* until after we also have move, to see if it is really claim */
9210             }
9211         } else if (gameMode == MachinePlaysWhite ||
9212                    gameMode == MachinePlaysBlack) {
9213           if (userOfferedDraw) {
9214             DisplayInformation(_("Machine accepts your draw offer"));
9215             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9216           } else {
9217             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9218           }
9219         }
9220     }
9221
9222
9223     /*
9224      * Look for thinking output
9225      */
9226     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9227           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9228                                 ) {
9229         int plylev, mvleft, mvtot, curscore, time;
9230         char mvname[MOVE_LEN];
9231         u64 nodes; // [DM]
9232         char plyext;
9233         int ignore = FALSE;
9234         int prefixHint = FALSE;
9235         mvname[0] = NULLCHAR;
9236
9237         switch (gameMode) {
9238           case MachinePlaysBlack:
9239           case IcsPlayingBlack:
9240             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9241             break;
9242           case MachinePlaysWhite:
9243           case IcsPlayingWhite:
9244             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9245             break;
9246           case AnalyzeMode:
9247           case AnalyzeFile:
9248             break;
9249           case IcsObserving: /* [DM] icsEngineAnalyze */
9250             if (!appData.icsEngineAnalyze) ignore = TRUE;
9251             break;
9252           case TwoMachinesPlay:
9253             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9254                 ignore = TRUE;
9255             }
9256             break;
9257           default:
9258             ignore = TRUE;
9259             break;
9260         }
9261
9262         if (!ignore) {
9263             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9264             buf1[0] = NULLCHAR;
9265             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9266                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9267
9268                 if (plyext != ' ' && plyext != '\t') {
9269                     time *= 100;
9270                 }
9271
9272                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9273                 if( cps->scoreIsAbsolute &&
9274                     ( gameMode == MachinePlaysBlack ||
9275                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9276                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9277                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9278                      !WhiteOnMove(currentMove)
9279                     ) )
9280                 {
9281                     curscore = -curscore;
9282                 }
9283
9284                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9285
9286                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9287                         char buf[MSG_SIZ];
9288                         FILE *f;
9289                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9290                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9291                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9292                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9293                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9294                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9295                                 fclose(f);
9296                         }
9297                         else
9298                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9299                           DisplayError(_("failed writing PV"), 0);
9300                 }
9301
9302                 tempStats.depth = plylev;
9303                 tempStats.nodes = nodes;
9304                 tempStats.time = time;
9305                 tempStats.score = curscore;
9306                 tempStats.got_only_move = 0;
9307
9308                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9309                         int ticklen;
9310
9311                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9312                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9313                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9314                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9315                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9316                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9317                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9318                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9319                 }
9320
9321                 /* Buffer overflow protection */
9322                 if (pv[0] != NULLCHAR) {
9323                     if (strlen(pv) >= sizeof(tempStats.movelist)
9324                         && appData.debugMode) {
9325                         fprintf(debugFP,
9326                                 "PV is too long; using the first %u bytes.\n",
9327                                 (unsigned) sizeof(tempStats.movelist) - 1);
9328                     }
9329
9330                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9331                 } else {
9332                     sprintf(tempStats.movelist, " no PV\n");
9333                 }
9334
9335                 if (tempStats.seen_stat) {
9336                     tempStats.ok_to_send = 1;
9337                 }
9338
9339                 if (strchr(tempStats.movelist, '(') != NULL) {
9340                     tempStats.line_is_book = 1;
9341                     tempStats.nr_moves = 0;
9342                     tempStats.moves_left = 0;
9343                 } else {
9344                     tempStats.line_is_book = 0;
9345                 }
9346
9347                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9348                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9349
9350                 SendProgramStatsToFrontend( cps, &tempStats );
9351
9352                 /*
9353                     [AS] Protect the thinkOutput buffer from overflow... this
9354                     is only useful if buf1 hasn't overflowed first!
9355                 */
9356                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9357                          plylev,
9358                          (gameMode == TwoMachinesPlay ?
9359                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9360                          ((double) curscore) / 100.0,
9361                          prefixHint ? lastHint : "",
9362                          prefixHint ? " " : "" );
9363
9364                 if( buf1[0] != NULLCHAR ) {
9365                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9366
9367                     if( strlen(pv) > max_len ) {
9368                         if( appData.debugMode) {
9369                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9370                         }
9371                         pv[max_len+1] = '\0';
9372                     }
9373
9374                     strcat( thinkOutput, pv);
9375                 }
9376
9377                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9378                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9379                     DisplayMove(currentMove - 1);
9380                 }
9381                 return;
9382
9383             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9384                 /* crafty (9.25+) says "(only move) <move>"
9385                  * if there is only 1 legal move
9386                  */
9387                 sscanf(p, "(only move) %s", buf1);
9388                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9389                 sprintf(programStats.movelist, "%s (only move)", buf1);
9390                 programStats.depth = 1;
9391                 programStats.nr_moves = 1;
9392                 programStats.moves_left = 1;
9393                 programStats.nodes = 1;
9394                 programStats.time = 1;
9395                 programStats.got_only_move = 1;
9396
9397                 /* Not really, but we also use this member to
9398                    mean "line isn't going to change" (Crafty
9399                    isn't searching, so stats won't change) */
9400                 programStats.line_is_book = 1;
9401
9402                 SendProgramStatsToFrontend( cps, &programStats );
9403
9404                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9405                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9406                     DisplayMove(currentMove - 1);
9407                 }
9408                 return;
9409             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9410                               &time, &nodes, &plylev, &mvleft,
9411                               &mvtot, mvname) >= 5) {
9412                 /* The stat01: line is from Crafty (9.29+) in response
9413                    to the "." command */
9414                 programStats.seen_stat = 1;
9415                 cps->maybeThinking = TRUE;
9416
9417                 if (programStats.got_only_move || !appData.periodicUpdates)
9418                   return;
9419
9420                 programStats.depth = plylev;
9421                 programStats.time = time;
9422                 programStats.nodes = nodes;
9423                 programStats.moves_left = mvleft;
9424                 programStats.nr_moves = mvtot;
9425                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9426                 programStats.ok_to_send = 1;
9427                 programStats.movelist[0] = '\0';
9428
9429                 SendProgramStatsToFrontend( cps, &programStats );
9430
9431                 return;
9432
9433             } else if (strncmp(message,"++",2) == 0) {
9434                 /* Crafty 9.29+ outputs this */
9435                 programStats.got_fail = 2;
9436                 return;
9437
9438             } else if (strncmp(message,"--",2) == 0) {
9439                 /* Crafty 9.29+ outputs this */
9440                 programStats.got_fail = 1;
9441                 return;
9442
9443             } else if (thinkOutput[0] != NULLCHAR &&
9444                        strncmp(message, "    ", 4) == 0) {
9445                 unsigned message_len;
9446
9447                 p = message;
9448                 while (*p && *p == ' ') p++;
9449
9450                 message_len = strlen( p );
9451
9452                 /* [AS] Avoid buffer overflow */
9453                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9454                     strcat(thinkOutput, " ");
9455                     strcat(thinkOutput, p);
9456                 }
9457
9458                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9459                     strcat(programStats.movelist, " ");
9460                     strcat(programStats.movelist, p);
9461                 }
9462
9463                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9464                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9465                     DisplayMove(currentMove - 1);
9466                 }
9467                 return;
9468             }
9469         }
9470         else {
9471             buf1[0] = NULLCHAR;
9472
9473             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9474                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9475             {
9476                 ChessProgramStats cpstats;
9477
9478                 if (plyext != ' ' && plyext != '\t') {
9479                     time *= 100;
9480                 }
9481
9482                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9483                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9484                     curscore = -curscore;
9485                 }
9486
9487                 cpstats.depth = plylev;
9488                 cpstats.nodes = nodes;
9489                 cpstats.time = time;
9490                 cpstats.score = curscore;
9491                 cpstats.got_only_move = 0;
9492                 cpstats.movelist[0] = '\0';
9493
9494                 if (buf1[0] != NULLCHAR) {
9495                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9496                 }
9497
9498                 cpstats.ok_to_send = 0;
9499                 cpstats.line_is_book = 0;
9500                 cpstats.nr_moves = 0;
9501                 cpstats.moves_left = 0;
9502
9503                 SendProgramStatsToFrontend( cps, &cpstats );
9504             }
9505         }
9506     }
9507 }
9508
9509
9510 /* Parse a game score from the character string "game", and
9511    record it as the history of the current game.  The game
9512    score is NOT assumed to start from the standard position.
9513    The display is not updated in any way.
9514    */
9515 void
9516 ParseGameHistory (char *game)
9517 {
9518     ChessMove moveType;
9519     int fromX, fromY, toX, toY, boardIndex;
9520     char promoChar;
9521     char *p, *q;
9522     char buf[MSG_SIZ];
9523
9524     if (appData.debugMode)
9525       fprintf(debugFP, "Parsing game history: %s\n", game);
9526
9527     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9528     gameInfo.site = StrSave(appData.icsHost);
9529     gameInfo.date = PGNDate();
9530     gameInfo.round = StrSave("-");
9531
9532     /* Parse out names of players */
9533     while (*game == ' ') game++;
9534     p = buf;
9535     while (*game != ' ') *p++ = *game++;
9536     *p = NULLCHAR;
9537     gameInfo.white = StrSave(buf);
9538     while (*game == ' ') game++;
9539     p = buf;
9540     while (*game != ' ' && *game != '\n') *p++ = *game++;
9541     *p = NULLCHAR;
9542     gameInfo.black = StrSave(buf);
9543
9544     /* Parse moves */
9545     boardIndex = blackPlaysFirst ? 1 : 0;
9546     yynewstr(game);
9547     for (;;) {
9548         yyboardindex = boardIndex;
9549         moveType = (ChessMove) Myylex();
9550         switch (moveType) {
9551           case IllegalMove:             /* maybe suicide chess, etc. */
9552   if (appData.debugMode) {
9553     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9554     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9555     setbuf(debugFP, NULL);
9556   }
9557           case WhitePromotion:
9558           case BlackPromotion:
9559           case WhiteNonPromotion:
9560           case BlackNonPromotion:
9561           case NormalMove:
9562           case FirstLeg:
9563           case WhiteCapturesEnPassant:
9564           case BlackCapturesEnPassant:
9565           case WhiteKingSideCastle:
9566           case WhiteQueenSideCastle:
9567           case BlackKingSideCastle:
9568           case BlackQueenSideCastle:
9569           case WhiteKingSideCastleWild:
9570           case WhiteQueenSideCastleWild:
9571           case BlackKingSideCastleWild:
9572           case BlackQueenSideCastleWild:
9573           /* PUSH Fabien */
9574           case WhiteHSideCastleFR:
9575           case WhiteASideCastleFR:
9576           case BlackHSideCastleFR:
9577           case BlackASideCastleFR:
9578           /* POP Fabien */
9579             fromX = currentMoveString[0] - AAA;
9580             fromY = currentMoveString[1] - ONE;
9581             toX = currentMoveString[2] - AAA;
9582             toY = currentMoveString[3] - ONE;
9583             promoChar = currentMoveString[4];
9584             break;
9585           case WhiteDrop:
9586           case BlackDrop:
9587             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9588             fromX = moveType == WhiteDrop ?
9589               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9590             (int) CharToPiece(ToLower(currentMoveString[0]));
9591             fromY = DROP_RANK;
9592             toX = currentMoveString[2] - AAA;
9593             toY = currentMoveString[3] - ONE;
9594             promoChar = NULLCHAR;
9595             break;
9596           case AmbiguousMove:
9597             /* bug? */
9598             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9599   if (appData.debugMode) {
9600     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9601     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9602     setbuf(debugFP, NULL);
9603   }
9604             DisplayError(buf, 0);
9605             return;
9606           case ImpossibleMove:
9607             /* bug? */
9608             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9609   if (appData.debugMode) {
9610     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9611     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9612     setbuf(debugFP, NULL);
9613   }
9614             DisplayError(buf, 0);
9615             return;
9616           case EndOfFile:
9617             if (boardIndex < backwardMostMove) {
9618                 /* Oops, gap.  How did that happen? */
9619                 DisplayError(_("Gap in move list"), 0);
9620                 return;
9621             }
9622             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9623             if (boardIndex > forwardMostMove) {
9624                 forwardMostMove = boardIndex;
9625             }
9626             return;
9627           case ElapsedTime:
9628             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9629                 strcat(parseList[boardIndex-1], " ");
9630                 strcat(parseList[boardIndex-1], yy_text);
9631             }
9632             continue;
9633           case Comment:
9634           case PGNTag:
9635           case NAG:
9636           default:
9637             /* ignore */
9638             continue;
9639           case WhiteWins:
9640           case BlackWins:
9641           case GameIsDrawn:
9642           case GameUnfinished:
9643             if (gameMode == IcsExamining) {
9644                 if (boardIndex < backwardMostMove) {
9645                     /* Oops, gap.  How did that happen? */
9646                     return;
9647                 }
9648                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9649                 return;
9650             }
9651             gameInfo.result = moveType;
9652             p = strchr(yy_text, '{');
9653             if (p == NULL) p = strchr(yy_text, '(');
9654             if (p == NULL) {
9655                 p = yy_text;
9656                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9657             } else {
9658                 q = strchr(p, *p == '{' ? '}' : ')');
9659                 if (q != NULL) *q = NULLCHAR;
9660                 p++;
9661             }
9662             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9663             gameInfo.resultDetails = StrSave(p);
9664             continue;
9665         }
9666         if (boardIndex >= forwardMostMove &&
9667             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9668             backwardMostMove = blackPlaysFirst ? 1 : 0;
9669             return;
9670         }
9671         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9672                                  fromY, fromX, toY, toX, promoChar,
9673                                  parseList[boardIndex]);
9674         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9675         /* currentMoveString is set as a side-effect of yylex */
9676         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9677         strcat(moveList[boardIndex], "\n");
9678         boardIndex++;
9679         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9680         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9681           case MT_NONE:
9682           case MT_STALEMATE:
9683           default:
9684             break;
9685           case MT_CHECK:
9686             if(gameInfo.variant != VariantShogi)
9687                 strcat(parseList[boardIndex - 1], "+");
9688             break;
9689           case MT_CHECKMATE:
9690           case MT_STAINMATE:
9691             strcat(parseList[boardIndex - 1], "#");
9692             break;
9693         }
9694     }
9695 }
9696
9697
9698 /* Apply a move to the given board  */
9699 void
9700 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9701 {
9702   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9703   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9704
9705     /* [HGM] compute & store e.p. status and castling rights for new position */
9706     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9707
9708       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9709       oldEP = (signed char)board[EP_STATUS];
9710       board[EP_STATUS] = EP_NONE;
9711
9712   if (fromY == DROP_RANK) {
9713         /* must be first */
9714         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9715             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9716             return;
9717         }
9718         piece = board[toY][toX] = (ChessSquare) fromX;
9719   } else {
9720       ChessSquare victim;
9721       int i;
9722
9723       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9724            victim = board[killY][killX],
9725            board[killY][killX] = EmptySquare,
9726            board[EP_STATUS] = EP_CAPTURE;
9727
9728       if( board[toY][toX] != EmptySquare ) {
9729            board[EP_STATUS] = EP_CAPTURE;
9730            if( (fromX != toX || fromY != toY) && // not igui!
9731                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9732                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9733                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9734            }
9735       }
9736
9737       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9738            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9739                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9740       } else
9741       if( board[fromY][fromX] == WhitePawn ) {
9742            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9743                board[EP_STATUS] = EP_PAWN_MOVE;
9744            if( toY-fromY==2) {
9745                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9746                         gameInfo.variant != VariantBerolina || toX < fromX)
9747                       board[EP_STATUS] = toX | berolina;
9748                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9749                         gameInfo.variant != VariantBerolina || toX > fromX)
9750                       board[EP_STATUS] = toX;
9751            }
9752       } else
9753       if( board[fromY][fromX] == BlackPawn ) {
9754            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9755                board[EP_STATUS] = EP_PAWN_MOVE;
9756            if( toY-fromY== -2) {
9757                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9758                         gameInfo.variant != VariantBerolina || toX < fromX)
9759                       board[EP_STATUS] = toX | berolina;
9760                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9761                         gameInfo.variant != VariantBerolina || toX > fromX)
9762                       board[EP_STATUS] = toX;
9763            }
9764        }
9765
9766        for(i=0; i<nrCastlingRights; i++) {
9767            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9768               board[CASTLING][i] == toX   && castlingRank[i] == toY
9769              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9770        }
9771
9772        if(gameInfo.variant == VariantSChess) { // update virginity
9773            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9774            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9775            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9776            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9777        }
9778
9779      if (fromX == toX && fromY == toY) return;
9780
9781      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9782      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9783      if(gameInfo.variant == VariantKnightmate)
9784          king += (int) WhiteUnicorn - (int) WhiteKing;
9785
9786     /* Code added by Tord: */
9787     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9788     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9789         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9790       board[fromY][fromX] = EmptySquare;
9791       board[toY][toX] = EmptySquare;
9792       if((toX > fromX) != (piece == WhiteRook)) {
9793         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9794       } else {
9795         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9796       }
9797     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9798                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9799       board[fromY][fromX] = EmptySquare;
9800       board[toY][toX] = EmptySquare;
9801       if((toX > fromX) != (piece == BlackRook)) {
9802         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9803       } else {
9804         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9805       }
9806     /* End of code added by Tord */
9807
9808     } else if (board[fromY][fromX] == king
9809         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9810         && toY == fromY && toX > fromX+1) {
9811         board[fromY][fromX] = EmptySquare;
9812         board[toY][toX] = king;
9813         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9814         board[fromY][BOARD_RGHT-1] = EmptySquare;
9815     } else if (board[fromY][fromX] == king
9816         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9817                && toY == fromY && toX < fromX-1) {
9818         board[fromY][fromX] = EmptySquare;
9819         board[toY][toX] = king;
9820         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9821         board[fromY][BOARD_LEFT] = EmptySquare;
9822     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9823                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9824                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9825                ) {
9826         /* white pawn promotion */
9827         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9828         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9829             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9830         board[fromY][fromX] = EmptySquare;
9831     } else if ((fromY >= BOARD_HEIGHT>>1)
9832                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9833                && (toX != fromX)
9834                && gameInfo.variant != VariantXiangqi
9835                && gameInfo.variant != VariantBerolina
9836                && (board[fromY][fromX] == WhitePawn)
9837                && (board[toY][toX] == EmptySquare)) {
9838         board[fromY][fromX] = EmptySquare;
9839         board[toY][toX] = WhitePawn;
9840         captured = board[toY - 1][toX];
9841         board[toY - 1][toX] = EmptySquare;
9842     } else if ((fromY == BOARD_HEIGHT-4)
9843                && (toX == fromX)
9844                && gameInfo.variant == VariantBerolina
9845                && (board[fromY][fromX] == WhitePawn)
9846                && (board[toY][toX] == EmptySquare)) {
9847         board[fromY][fromX] = EmptySquare;
9848         board[toY][toX] = WhitePawn;
9849         if(oldEP & EP_BEROLIN_A) {
9850                 captured = board[fromY][fromX-1];
9851                 board[fromY][fromX-1] = EmptySquare;
9852         }else{  captured = board[fromY][fromX+1];
9853                 board[fromY][fromX+1] = EmptySquare;
9854         }
9855     } else if (board[fromY][fromX] == king
9856         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9857                && toY == fromY && toX > fromX+1) {
9858         board[fromY][fromX] = EmptySquare;
9859         board[toY][toX] = king;
9860         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9861         board[fromY][BOARD_RGHT-1] = EmptySquare;
9862     } else if (board[fromY][fromX] == king
9863         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9864                && toY == fromY && toX < fromX-1) {
9865         board[fromY][fromX] = EmptySquare;
9866         board[toY][toX] = king;
9867         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9868         board[fromY][BOARD_LEFT] = EmptySquare;
9869     } else if (fromY == 7 && fromX == 3
9870                && board[fromY][fromX] == BlackKing
9871                && toY == 7 && toX == 5) {
9872         board[fromY][fromX] = EmptySquare;
9873         board[toY][toX] = BlackKing;
9874         board[fromY][7] = EmptySquare;
9875         board[toY][4] = BlackRook;
9876     } else if (fromY == 7 && fromX == 3
9877                && board[fromY][fromX] == BlackKing
9878                && toY == 7 && toX == 1) {
9879         board[fromY][fromX] = EmptySquare;
9880         board[toY][toX] = BlackKing;
9881         board[fromY][0] = EmptySquare;
9882         board[toY][2] = BlackRook;
9883     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9884                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9885                && toY < promoRank && promoChar
9886                ) {
9887         /* black pawn promotion */
9888         board[toY][toX] = CharToPiece(ToLower(promoChar));
9889         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9890             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9891         board[fromY][fromX] = EmptySquare;
9892     } else if ((fromY < BOARD_HEIGHT>>1)
9893                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9894                && (toX != fromX)
9895                && gameInfo.variant != VariantXiangqi
9896                && gameInfo.variant != VariantBerolina
9897                && (board[fromY][fromX] == BlackPawn)
9898                && (board[toY][toX] == EmptySquare)) {
9899         board[fromY][fromX] = EmptySquare;
9900         board[toY][toX] = BlackPawn;
9901         captured = board[toY + 1][toX];
9902         board[toY + 1][toX] = EmptySquare;
9903     } else if ((fromY == 3)
9904                && (toX == fromX)
9905                && gameInfo.variant == VariantBerolina
9906                && (board[fromY][fromX] == BlackPawn)
9907                && (board[toY][toX] == EmptySquare)) {
9908         board[fromY][fromX] = EmptySquare;
9909         board[toY][toX] = BlackPawn;
9910         if(oldEP & EP_BEROLIN_A) {
9911                 captured = board[fromY][fromX-1];
9912                 board[fromY][fromX-1] = EmptySquare;
9913         }else{  captured = board[fromY][fromX+1];
9914                 board[fromY][fromX+1] = EmptySquare;
9915         }
9916     } else {
9917         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9918         board[fromY][fromX] = EmptySquare;
9919         board[toY][toX] = piece;
9920     }
9921   }
9922
9923     if (gameInfo.holdingsWidth != 0) {
9924
9925       /* !!A lot more code needs to be written to support holdings  */
9926       /* [HGM] OK, so I have written it. Holdings are stored in the */
9927       /* penultimate board files, so they are automaticlly stored   */
9928       /* in the game history.                                       */
9929       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9930                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9931         /* Delete from holdings, by decreasing count */
9932         /* and erasing image if necessary            */
9933         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9934         if(p < (int) BlackPawn) { /* white drop */
9935              p -= (int)WhitePawn;
9936                  p = PieceToNumber((ChessSquare)p);
9937              if(p >= gameInfo.holdingsSize) p = 0;
9938              if(--board[p][BOARD_WIDTH-2] <= 0)
9939                   board[p][BOARD_WIDTH-1] = EmptySquare;
9940              if((int)board[p][BOARD_WIDTH-2] < 0)
9941                         board[p][BOARD_WIDTH-2] = 0;
9942         } else {                  /* black drop */
9943              p -= (int)BlackPawn;
9944                  p = PieceToNumber((ChessSquare)p);
9945              if(p >= gameInfo.holdingsSize) p = 0;
9946              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9947                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9948              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9949                         board[BOARD_HEIGHT-1-p][1] = 0;
9950         }
9951       }
9952       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9953           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9954         /* [HGM] holdings: Add to holdings, if holdings exist */
9955         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9956                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9957                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9958         }
9959         p = (int) captured;
9960         if (p >= (int) BlackPawn) {
9961           p -= (int)BlackPawn;
9962           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9963                   /* in Shogi restore piece to its original  first */
9964                   captured = (ChessSquare) (DEMOTED captured);
9965                   p = DEMOTED p;
9966           }
9967           p = PieceToNumber((ChessSquare)p);
9968           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9969           board[p][BOARD_WIDTH-2]++;
9970           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9971         } else {
9972           p -= (int)WhitePawn;
9973           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9974                   captured = (ChessSquare) (DEMOTED captured);
9975                   p = DEMOTED p;
9976           }
9977           p = PieceToNumber((ChessSquare)p);
9978           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9979           board[BOARD_HEIGHT-1-p][1]++;
9980           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9981         }
9982       }
9983     } else if (gameInfo.variant == VariantAtomic) {
9984       if (captured != EmptySquare) {
9985         int y, x;
9986         for (y = toY-1; y <= toY+1; y++) {
9987           for (x = toX-1; x <= toX+1; x++) {
9988             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9989                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9990               board[y][x] = EmptySquare;
9991             }
9992           }
9993         }
9994         board[toY][toX] = EmptySquare;
9995       }
9996     }
9997     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9998         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9999     } else
10000     if(promoChar == '+') {
10001         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10002         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10003         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10004           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10005     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10006         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10007         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10008            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10009         board[toY][toX] = newPiece;
10010     }
10011     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10012                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10013         // [HGM] superchess: take promotion piece out of holdings
10014         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10015         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10016             if(!--board[k][BOARD_WIDTH-2])
10017                 board[k][BOARD_WIDTH-1] = EmptySquare;
10018         } else {
10019             if(!--board[BOARD_HEIGHT-1-k][1])
10020                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10021         }
10022     }
10023 }
10024
10025 /* Updates forwardMostMove */
10026 void
10027 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10028 {
10029     int x = toX, y = toY;
10030     char *s = parseList[forwardMostMove];
10031     ChessSquare p = boards[forwardMostMove][toY][toX];
10032 //    forwardMostMove++; // [HGM] bare: moved downstream
10033
10034     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10035     (void) CoordsToAlgebraic(boards[forwardMostMove],
10036                              PosFlags(forwardMostMove),
10037                              fromY, fromX, y, x, promoChar,
10038                              s);
10039     if(killX >= 0 && killY >= 0)
10040         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10041
10042     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10043         int timeLeft; static int lastLoadFlag=0; int king, piece;
10044         piece = boards[forwardMostMove][fromY][fromX];
10045         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10046         if(gameInfo.variant == VariantKnightmate)
10047             king += (int) WhiteUnicorn - (int) WhiteKing;
10048         if(forwardMostMove == 0) {
10049             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10050                 fprintf(serverMoves, "%s;", UserName());
10051             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10052                 fprintf(serverMoves, "%s;", second.tidy);
10053             fprintf(serverMoves, "%s;", first.tidy);
10054             if(gameMode == MachinePlaysWhite)
10055                 fprintf(serverMoves, "%s;", UserName());
10056             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10057                 fprintf(serverMoves, "%s;", second.tidy);
10058         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10059         lastLoadFlag = loadFlag;
10060         // print base move
10061         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10062         // print castling suffix
10063         if( toY == fromY && piece == king ) {
10064             if(toX-fromX > 1)
10065                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10066             if(fromX-toX >1)
10067                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10068         }
10069         // e.p. suffix
10070         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10071              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10072              boards[forwardMostMove][toY][toX] == EmptySquare
10073              && fromX != toX && fromY != toY)
10074                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10075         // promotion suffix
10076         if(promoChar != NULLCHAR) {
10077             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10078                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10079                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10080             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10081         }
10082         if(!loadFlag) {
10083                 char buf[MOVE_LEN*2], *p; int len;
10084             fprintf(serverMoves, "/%d/%d",
10085                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10086             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10087             else                      timeLeft = blackTimeRemaining/1000;
10088             fprintf(serverMoves, "/%d", timeLeft);
10089                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10090                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10091                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10092                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10093             fprintf(serverMoves, "/%s", buf);
10094         }
10095         fflush(serverMoves);
10096     }
10097
10098     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10099         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10100       return;
10101     }
10102     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10103     if (commentList[forwardMostMove+1] != NULL) {
10104         free(commentList[forwardMostMove+1]);
10105         commentList[forwardMostMove+1] = NULL;
10106     }
10107     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10108     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10109     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10110     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10111     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10112     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10113     adjustedClock = FALSE;
10114     gameInfo.result = GameUnfinished;
10115     if (gameInfo.resultDetails != NULL) {
10116         free(gameInfo.resultDetails);
10117         gameInfo.resultDetails = NULL;
10118     }
10119     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10120                               moveList[forwardMostMove - 1]);
10121     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10122       case MT_NONE:
10123       case MT_STALEMATE:
10124       default:
10125         break;
10126       case MT_CHECK:
10127         if(gameInfo.variant != VariantShogi)
10128             strcat(parseList[forwardMostMove - 1], "+");
10129         break;
10130       case MT_CHECKMATE:
10131       case MT_STAINMATE:
10132         strcat(parseList[forwardMostMove - 1], "#");
10133         break;
10134     }
10135 }
10136
10137 /* Updates currentMove if not pausing */
10138 void
10139 ShowMove (int fromX, int fromY, int toX, int toY)
10140 {
10141     int instant = (gameMode == PlayFromGameFile) ?
10142         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10143     if(appData.noGUI) return;
10144     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10145         if (!instant) {
10146             if (forwardMostMove == currentMove + 1) {
10147                 AnimateMove(boards[forwardMostMove - 1],
10148                             fromX, fromY, toX, toY);
10149             }
10150         }
10151         currentMove = forwardMostMove;
10152     }
10153
10154     killX = killY = -1; // [HGM] lion: used up
10155
10156     if (instant) return;
10157
10158     DisplayMove(currentMove - 1);
10159     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10160             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10161                 SetHighlights(fromX, fromY, toX, toY);
10162             }
10163     }
10164     DrawPosition(FALSE, boards[currentMove]);
10165     DisplayBothClocks();
10166     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10167 }
10168
10169 void
10170 SendEgtPath (ChessProgramState *cps)
10171 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10172         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10173
10174         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10175
10176         while(*p) {
10177             char c, *q = name+1, *r, *s;
10178
10179             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10180             while(*p && *p != ',') *q++ = *p++;
10181             *q++ = ':'; *q = 0;
10182             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10183                 strcmp(name, ",nalimov:") == 0 ) {
10184                 // take nalimov path from the menu-changeable option first, if it is defined
10185               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10186                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10187             } else
10188             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10189                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10190                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10191                 s = r = StrStr(s, ":") + 1; // beginning of path info
10192                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10193                 c = *r; *r = 0;             // temporarily null-terminate path info
10194                     *--q = 0;               // strip of trailig ':' from name
10195                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10196                 *r = c;
10197                 SendToProgram(buf,cps);     // send egtbpath command for this format
10198             }
10199             if(*p == ',') p++; // read away comma to position for next format name
10200         }
10201 }
10202
10203 static int
10204 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10205 {
10206       int width = 8, height = 8, holdings = 0;             // most common sizes
10207       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10208       // correct the deviations default for each variant
10209       if( v == VariantXiangqi ) width = 9,  height = 10;
10210       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10211       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10212       if( v == VariantCapablanca || v == VariantCapaRandom ||
10213           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10214                                 width = 10;
10215       if( v == VariantCourier ) width = 12;
10216       if( v == VariantSuper )                            holdings = 8;
10217       if( v == VariantGreat )   width = 10,              holdings = 8;
10218       if( v == VariantSChess )                           holdings = 7;
10219       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10220       if( v == VariantChuChess) width = 10, height = 10;
10221       if( v == VariantChu )     width = 12, height = 12;
10222       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10223              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10224              holdingsSize >= 0 && holdingsSize != holdings;
10225 }
10226
10227 char variantError[MSG_SIZ];
10228
10229 char *
10230 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10231 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10232       char *p, *variant = VariantName(v);
10233       static char b[MSG_SIZ];
10234       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10235            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10236                                                holdingsSize, variant); // cook up sized variant name
10237            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10238            if(StrStr(list, b) == NULL) {
10239                // specific sized variant not known, check if general sizing allowed
10240                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10241                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10242                             boardWidth, boardHeight, holdingsSize, engine);
10243                    return NULL;
10244                }
10245                /* [HGM] here we really should compare with the maximum supported board size */
10246            }
10247       } else snprintf(b, MSG_SIZ,"%s", variant);
10248       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10249       p = StrStr(list, b);
10250       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10251       if(p == NULL) {
10252           // occurs not at all in list, or only as sub-string
10253           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10254           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10255               int l = strlen(variantError);
10256               char *q;
10257               while(p != list && p[-1] != ',') p--;
10258               q = strchr(p, ',');
10259               if(q) *q = NULLCHAR;
10260               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10261               if(q) *q= ',';
10262           }
10263           return NULL;
10264       }
10265       return b;
10266 }
10267
10268 void
10269 InitChessProgram (ChessProgramState *cps, int setup)
10270 /* setup needed to setup FRC opening position */
10271 {
10272     char buf[MSG_SIZ], *b;
10273     if (appData.noChessProgram) return;
10274     hintRequested = FALSE;
10275     bookRequested = FALSE;
10276
10277     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10278     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10279     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10280     if(cps->memSize) { /* [HGM] memory */
10281       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10282         SendToProgram(buf, cps);
10283     }
10284     SendEgtPath(cps); /* [HGM] EGT */
10285     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10286       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10287         SendToProgram(buf, cps);
10288     }
10289
10290     SendToProgram(cps->initString, cps);
10291     if (gameInfo.variant != VariantNormal &&
10292         gameInfo.variant != VariantLoadable
10293         /* [HGM] also send variant if board size non-standard */
10294         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10295
10296       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10297                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10298       if (b == NULL) {
10299         DisplayFatalError(variantError, 0, 1);
10300         return;
10301       }
10302
10303       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10304       SendToProgram(buf, cps);
10305     }
10306     currentlyInitializedVariant = gameInfo.variant;
10307
10308     /* [HGM] send opening position in FRC to first engine */
10309     if(setup) {
10310           SendToProgram("force\n", cps);
10311           SendBoard(cps, 0);
10312           /* engine is now in force mode! Set flag to wake it up after first move. */
10313           setboardSpoiledMachineBlack = 1;
10314     }
10315
10316     if (cps->sendICS) {
10317       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10318       SendToProgram(buf, cps);
10319     }
10320     cps->maybeThinking = FALSE;
10321     cps->offeredDraw = 0;
10322     if (!appData.icsActive) {
10323         SendTimeControl(cps, movesPerSession, timeControl,
10324                         timeIncrement, appData.searchDepth,
10325                         searchTime);
10326     }
10327     if (appData.showThinking
10328         // [HGM] thinking: four options require thinking output to be sent
10329         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10330                                 ) {
10331         SendToProgram("post\n", cps);
10332     }
10333     SendToProgram("hard\n", cps);
10334     if (!appData.ponderNextMove) {
10335         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10336            it without being sure what state we are in first.  "hard"
10337            is not a toggle, so that one is OK.
10338          */
10339         SendToProgram("easy\n", cps);
10340     }
10341     if (cps->usePing) {
10342       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10343       SendToProgram(buf, cps);
10344     }
10345     cps->initDone = TRUE;
10346     ClearEngineOutputPane(cps == &second);
10347 }
10348
10349
10350 void
10351 ResendOptions (ChessProgramState *cps)
10352 { // send the stored value of the options
10353   int i;
10354   char buf[MSG_SIZ];
10355   Option *opt = cps->option;
10356   for(i=0; i<cps->nrOptions; i++, opt++) {
10357       switch(opt->type) {
10358         case Spin:
10359         case Slider:
10360         case CheckBox:
10361             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10362           break;
10363         case ComboBox:
10364           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10365           break;
10366         default:
10367             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10368           break;
10369         case Button:
10370         case SaveButton:
10371           continue;
10372       }
10373       SendToProgram(buf, cps);
10374   }
10375 }
10376
10377 void
10378 StartChessProgram (ChessProgramState *cps)
10379 {
10380     char buf[MSG_SIZ];
10381     int err;
10382
10383     if (appData.noChessProgram) return;
10384     cps->initDone = FALSE;
10385
10386     if (strcmp(cps->host, "localhost") == 0) {
10387         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10388     } else if (*appData.remoteShell == NULLCHAR) {
10389         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10390     } else {
10391         if (*appData.remoteUser == NULLCHAR) {
10392           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10393                     cps->program);
10394         } else {
10395           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10396                     cps->host, appData.remoteUser, cps->program);
10397         }
10398         err = StartChildProcess(buf, "", &cps->pr);
10399     }
10400
10401     if (err != 0) {
10402       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10403         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10404         if(cps != &first) return;
10405         appData.noChessProgram = TRUE;
10406         ThawUI();
10407         SetNCPMode();
10408 //      DisplayFatalError(buf, err, 1);
10409 //      cps->pr = NoProc;
10410 //      cps->isr = NULL;
10411         return;
10412     }
10413
10414     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10415     if (cps->protocolVersion > 1) {
10416       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10417       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10418         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10419         cps->comboCnt = 0;  //                and values of combo boxes
10420       }
10421       SendToProgram(buf, cps);
10422       if(cps->reload) ResendOptions(cps);
10423     } else {
10424       SendToProgram("xboard\n", cps);
10425     }
10426 }
10427
10428 void
10429 TwoMachinesEventIfReady P((void))
10430 {
10431   static int curMess = 0;
10432   if (first.lastPing != first.lastPong) {
10433     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10434     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10435     return;
10436   }
10437   if (second.lastPing != second.lastPong) {
10438     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10439     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10440     return;
10441   }
10442   DisplayMessage("", ""); curMess = 0;
10443   TwoMachinesEvent();
10444 }
10445
10446 char *
10447 MakeName (char *template)
10448 {
10449     time_t clock;
10450     struct tm *tm;
10451     static char buf[MSG_SIZ];
10452     char *p = buf;
10453     int i;
10454
10455     clock = time((time_t *)NULL);
10456     tm = localtime(&clock);
10457
10458     while(*p++ = *template++) if(p[-1] == '%') {
10459         switch(*template++) {
10460           case 0:   *p = 0; return buf;
10461           case 'Y': i = tm->tm_year+1900; break;
10462           case 'y': i = tm->tm_year-100; break;
10463           case 'M': i = tm->tm_mon+1; break;
10464           case 'd': i = tm->tm_mday; break;
10465           case 'h': i = tm->tm_hour; break;
10466           case 'm': i = tm->tm_min; break;
10467           case 's': i = tm->tm_sec; break;
10468           default:  i = 0;
10469         }
10470         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10471     }
10472     return buf;
10473 }
10474
10475 int
10476 CountPlayers (char *p)
10477 {
10478     int n = 0;
10479     while(p = strchr(p, '\n')) p++, n++; // count participants
10480     return n;
10481 }
10482
10483 FILE *
10484 WriteTourneyFile (char *results, FILE *f)
10485 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10486     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10487     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10488         // create a file with tournament description
10489         fprintf(f, "-participants {%s}\n", appData.participants);
10490         fprintf(f, "-seedBase %d\n", appData.seedBase);
10491         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10492         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10493         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10494         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10495         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10496         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10497         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10498         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10499         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10500         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10501         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10502         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10503         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10504         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10505         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10506         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10507         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10508         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10509         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10510         fprintf(f, "-smpCores %d\n", appData.smpCores);
10511         if(searchTime > 0)
10512                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10513         else {
10514                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10515                 fprintf(f, "-tc %s\n", appData.timeControl);
10516                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10517         }
10518         fprintf(f, "-results \"%s\"\n", results);
10519     }
10520     return f;
10521 }
10522
10523 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10524
10525 void
10526 Substitute (char *participants, int expunge)
10527 {
10528     int i, changed, changes=0, nPlayers=0;
10529     char *p, *q, *r, buf[MSG_SIZ];
10530     if(participants == NULL) return;
10531     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10532     r = p = participants; q = appData.participants;
10533     while(*p && *p == *q) {
10534         if(*p == '\n') r = p+1, nPlayers++;
10535         p++; q++;
10536     }
10537     if(*p) { // difference
10538         while(*p && *p++ != '\n');
10539         while(*q && *q++ != '\n');
10540       changed = nPlayers;
10541         changes = 1 + (strcmp(p, q) != 0);
10542     }
10543     if(changes == 1) { // a single engine mnemonic was changed
10544         q = r; while(*q) nPlayers += (*q++ == '\n');
10545         p = buf; while(*r && (*p = *r++) != '\n') p++;
10546         *p = NULLCHAR;
10547         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10548         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10549         if(mnemonic[i]) { // The substitute is valid
10550             FILE *f;
10551             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10552                 flock(fileno(f), LOCK_EX);
10553                 ParseArgsFromFile(f);
10554                 fseek(f, 0, SEEK_SET);
10555                 FREE(appData.participants); appData.participants = participants;
10556                 if(expunge) { // erase results of replaced engine
10557                     int len = strlen(appData.results), w, b, dummy;
10558                     for(i=0; i<len; i++) {
10559                         Pairing(i, nPlayers, &w, &b, &dummy);
10560                         if((w == changed || b == changed) && appData.results[i] == '*') {
10561                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10562                             fclose(f);
10563                             return;
10564                         }
10565                     }
10566                     for(i=0; i<len; i++) {
10567                         Pairing(i, nPlayers, &w, &b, &dummy);
10568                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10569                     }
10570                 }
10571                 WriteTourneyFile(appData.results, f);
10572                 fclose(f); // release lock
10573                 return;
10574             }
10575         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10576     }
10577     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10578     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10579     free(participants);
10580     return;
10581 }
10582
10583 int
10584 CheckPlayers (char *participants)
10585 {
10586         int i;
10587         char buf[MSG_SIZ], *p;
10588         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10589         while(p = strchr(participants, '\n')) {
10590             *p = NULLCHAR;
10591             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10592             if(!mnemonic[i]) {
10593                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10594                 *p = '\n';
10595                 DisplayError(buf, 0);
10596                 return 1;
10597             }
10598             *p = '\n';
10599             participants = p + 1;
10600         }
10601         return 0;
10602 }
10603
10604 int
10605 CreateTourney (char *name)
10606 {
10607         FILE *f;
10608         if(matchMode && strcmp(name, appData.tourneyFile)) {
10609              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10610         }
10611         if(name[0] == NULLCHAR) {
10612             if(appData.participants[0])
10613                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10614             return 0;
10615         }
10616         f = fopen(name, "r");
10617         if(f) { // file exists
10618             ASSIGN(appData.tourneyFile, name);
10619             ParseArgsFromFile(f); // parse it
10620         } else {
10621             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10622             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10623                 DisplayError(_("Not enough participants"), 0);
10624                 return 0;
10625             }
10626             if(CheckPlayers(appData.participants)) return 0;
10627             ASSIGN(appData.tourneyFile, name);
10628             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10629             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10630         }
10631         fclose(f);
10632         appData.noChessProgram = FALSE;
10633         appData.clockMode = TRUE;
10634         SetGNUMode();
10635         return 1;
10636 }
10637
10638 int
10639 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10640 {
10641     char buf[MSG_SIZ], *p, *q;
10642     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10643     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10644     skip = !all && group[0]; // if group requested, we start in skip mode
10645     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10646         p = names; q = buf; header = 0;
10647         while(*p && *p != '\n') *q++ = *p++;
10648         *q = 0;
10649         if(*p == '\n') p++;
10650         if(buf[0] == '#') {
10651             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10652             depth++; // we must be entering a new group
10653             if(all) continue; // suppress printing group headers when complete list requested
10654             header = 1;
10655             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10656         }
10657         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10658         if(engineList[i]) free(engineList[i]);
10659         engineList[i] = strdup(buf);
10660         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10661         if(engineMnemonic[i]) free(engineMnemonic[i]);
10662         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10663             strcat(buf, " (");
10664             sscanf(q + 8, "%s", buf + strlen(buf));
10665             strcat(buf, ")");
10666         }
10667         engineMnemonic[i] = strdup(buf);
10668         i++;
10669     }
10670     engineList[i] = engineMnemonic[i] = NULL;
10671     return i;
10672 }
10673
10674 // following implemented as macro to avoid type limitations
10675 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10676
10677 void
10678 SwapEngines (int n)
10679 {   // swap settings for first engine and other engine (so far only some selected options)
10680     int h;
10681     char *p;
10682     if(n == 0) return;
10683     SWAP(directory, p)
10684     SWAP(chessProgram, p)
10685     SWAP(isUCI, h)
10686     SWAP(hasOwnBookUCI, h)
10687     SWAP(protocolVersion, h)
10688     SWAP(reuse, h)
10689     SWAP(scoreIsAbsolute, h)
10690     SWAP(timeOdds, h)
10691     SWAP(logo, p)
10692     SWAP(pgnName, p)
10693     SWAP(pvSAN, h)
10694     SWAP(engOptions, p)
10695     SWAP(engInitString, p)
10696     SWAP(computerString, p)
10697     SWAP(features, p)
10698     SWAP(fenOverride, p)
10699     SWAP(NPS, h)
10700     SWAP(accumulateTC, h)
10701     SWAP(host, p)
10702 }
10703
10704 int
10705 GetEngineLine (char *s, int n)
10706 {
10707     int i;
10708     char buf[MSG_SIZ];
10709     extern char *icsNames;
10710     if(!s || !*s) return 0;
10711     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10712     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10713     if(!mnemonic[i]) return 0;
10714     if(n == 11) return 1; // just testing if there was a match
10715     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10716     if(n == 1) SwapEngines(n);
10717     ParseArgsFromString(buf);
10718     if(n == 1) SwapEngines(n);
10719     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10720         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10721         ParseArgsFromString(buf);
10722     }
10723     return 1;
10724 }
10725
10726 int
10727 SetPlayer (int player, char *p)
10728 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10729     int i;
10730     char buf[MSG_SIZ], *engineName;
10731     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10732     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10733     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10734     if(mnemonic[i]) {
10735         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10736         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10737         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10738         ParseArgsFromString(buf);
10739     } else { // no engine with this nickname is installed!
10740         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10741         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10742         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10743         ModeHighlight();
10744         DisplayError(buf, 0);
10745         return 0;
10746     }
10747     free(engineName);
10748     return i;
10749 }
10750
10751 char *recentEngines;
10752
10753 void
10754 RecentEngineEvent (int nr)
10755 {
10756     int n;
10757 //    SwapEngines(1); // bump first to second
10758 //    ReplaceEngine(&second, 1); // and load it there
10759     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10760     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10761     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10762         ReplaceEngine(&first, 0);
10763         FloatToFront(&appData.recentEngineList, command[n]);
10764     }
10765 }
10766
10767 int
10768 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10769 {   // determine players from game number
10770     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10771
10772     if(appData.tourneyType == 0) {
10773         roundsPerCycle = (nPlayers - 1) | 1;
10774         pairingsPerRound = nPlayers / 2;
10775     } else if(appData.tourneyType > 0) {
10776         roundsPerCycle = nPlayers - appData.tourneyType;
10777         pairingsPerRound = appData.tourneyType;
10778     }
10779     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10780     gamesPerCycle = gamesPerRound * roundsPerCycle;
10781     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10782     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10783     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10784     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10785     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10786     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10787
10788     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10789     if(appData.roundSync) *syncInterval = gamesPerRound;
10790
10791     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10792
10793     if(appData.tourneyType == 0) {
10794         if(curPairing == (nPlayers-1)/2 ) {
10795             *whitePlayer = curRound;
10796             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10797         } else {
10798             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10799             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10800             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10801             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10802         }
10803     } else if(appData.tourneyType > 1) {
10804         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10805         *whitePlayer = curRound + appData.tourneyType;
10806     } else if(appData.tourneyType > 0) {
10807         *whitePlayer = curPairing;
10808         *blackPlayer = curRound + appData.tourneyType;
10809     }
10810
10811     // take care of white/black alternation per round.
10812     // For cycles and games this is already taken care of by default, derived from matchGame!
10813     return curRound & 1;
10814 }
10815
10816 int
10817 NextTourneyGame (int nr, int *swapColors)
10818 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10819     char *p, *q;
10820     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10821     FILE *tf;
10822     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10823     tf = fopen(appData.tourneyFile, "r");
10824     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10825     ParseArgsFromFile(tf); fclose(tf);
10826     InitTimeControls(); // TC might be altered from tourney file
10827
10828     nPlayers = CountPlayers(appData.participants); // count participants
10829     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10830     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10831
10832     if(syncInterval) {
10833         p = q = appData.results;
10834         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10835         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10836             DisplayMessage(_("Waiting for other game(s)"),"");
10837             waitingForGame = TRUE;
10838             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10839             return 0;
10840         }
10841         waitingForGame = FALSE;
10842     }
10843
10844     if(appData.tourneyType < 0) {
10845         if(nr>=0 && !pairingReceived) {
10846             char buf[1<<16];
10847             if(pairing.pr == NoProc) {
10848                 if(!appData.pairingEngine[0]) {
10849                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10850                     return 0;
10851                 }
10852                 StartChessProgram(&pairing); // starts the pairing engine
10853             }
10854             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10855             SendToProgram(buf, &pairing);
10856             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10857             SendToProgram(buf, &pairing);
10858             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10859         }
10860         pairingReceived = 0;                              // ... so we continue here
10861         *swapColors = 0;
10862         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10863         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10864         matchGame = 1; roundNr = nr / syncInterval + 1;
10865     }
10866
10867     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10868
10869     // redefine engines, engine dir, etc.
10870     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10871     if(first.pr == NoProc) {
10872       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10873       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10874     }
10875     if(second.pr == NoProc) {
10876       SwapEngines(1);
10877       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10878       SwapEngines(1);         // and make that valid for second engine by swapping
10879       InitEngine(&second, 1);
10880     }
10881     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10882     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10883     return OK;
10884 }
10885
10886 void
10887 NextMatchGame ()
10888 {   // performs game initialization that does not invoke engines, and then tries to start the game
10889     int res, firstWhite, swapColors = 0;
10890     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10891     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
10892         char buf[MSG_SIZ];
10893         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10894         if(strcmp(buf, currentDebugFile)) { // name has changed
10895             FILE *f = fopen(buf, "w");
10896             if(f) { // if opening the new file failed, just keep using the old one
10897                 ASSIGN(currentDebugFile, buf);
10898                 fclose(debugFP);
10899                 debugFP = f;
10900             }
10901             if(appData.serverFileName) {
10902                 if(serverFP) fclose(serverFP);
10903                 serverFP = fopen(appData.serverFileName, "w");
10904                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10905                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10906             }
10907         }
10908     }
10909     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10910     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10911     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10912     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10913     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10914     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10915     Reset(FALSE, first.pr != NoProc);
10916     res = LoadGameOrPosition(matchGame); // setup game
10917     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10918     if(!res) return; // abort when bad game/pos file
10919     TwoMachinesEvent();
10920 }
10921
10922 void
10923 UserAdjudicationEvent (int result)
10924 {
10925     ChessMove gameResult = GameIsDrawn;
10926
10927     if( result > 0 ) {
10928         gameResult = WhiteWins;
10929     }
10930     else if( result < 0 ) {
10931         gameResult = BlackWins;
10932     }
10933
10934     if( gameMode == TwoMachinesPlay ) {
10935         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10936     }
10937 }
10938
10939
10940 // [HGM] save: calculate checksum of game to make games easily identifiable
10941 int
10942 StringCheckSum (char *s)
10943 {
10944         int i = 0;
10945         if(s==NULL) return 0;
10946         while(*s) i = i*259 + *s++;
10947         return i;
10948 }
10949
10950 int
10951 GameCheckSum ()
10952 {
10953         int i, sum=0;
10954         for(i=backwardMostMove; i<forwardMostMove; i++) {
10955                 sum += pvInfoList[i].depth;
10956                 sum += StringCheckSum(parseList[i]);
10957                 sum += StringCheckSum(commentList[i]);
10958                 sum *= 261;
10959         }
10960         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10961         return sum + StringCheckSum(commentList[i]);
10962 } // end of save patch
10963
10964 void
10965 GameEnds (ChessMove result, char *resultDetails, int whosays)
10966 {
10967     GameMode nextGameMode;
10968     int isIcsGame;
10969     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10970
10971     if(endingGame) return; /* [HGM] crash: forbid recursion */
10972     endingGame = 1;
10973     if(twoBoards) { // [HGM] dual: switch back to one board
10974         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10975         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10976     }
10977     if (appData.debugMode) {
10978       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10979               result, resultDetails ? resultDetails : "(null)", whosays);
10980     }
10981
10982     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10983
10984     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10985
10986     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10987         /* If we are playing on ICS, the server decides when the
10988            game is over, but the engine can offer to draw, claim
10989            a draw, or resign.
10990          */
10991 #if ZIPPY
10992         if (appData.zippyPlay && first.initDone) {
10993             if (result == GameIsDrawn) {
10994                 /* In case draw still needs to be claimed */
10995                 SendToICS(ics_prefix);
10996                 SendToICS("draw\n");
10997             } else if (StrCaseStr(resultDetails, "resign")) {
10998                 SendToICS(ics_prefix);
10999                 SendToICS("resign\n");
11000             }
11001         }
11002 #endif
11003         endingGame = 0; /* [HGM] crash */
11004         return;
11005     }
11006
11007     /* If we're loading the game from a file, stop */
11008     if (whosays == GE_FILE) {
11009       (void) StopLoadGameTimer();
11010       gameFileFP = NULL;
11011     }
11012
11013     /* Cancel draw offers */
11014     first.offeredDraw = second.offeredDraw = 0;
11015
11016     /* If this is an ICS game, only ICS can really say it's done;
11017        if not, anyone can. */
11018     isIcsGame = (gameMode == IcsPlayingWhite ||
11019                  gameMode == IcsPlayingBlack ||
11020                  gameMode == IcsObserving    ||
11021                  gameMode == IcsExamining);
11022
11023     if (!isIcsGame || whosays == GE_ICS) {
11024         /* OK -- not an ICS game, or ICS said it was done */
11025         StopClocks();
11026         if (!isIcsGame && !appData.noChessProgram)
11027           SetUserThinkingEnables();
11028
11029         /* [HGM] if a machine claims the game end we verify this claim */
11030         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11031             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11032                 char claimer;
11033                 ChessMove trueResult = (ChessMove) -1;
11034
11035                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11036                                             first.twoMachinesColor[0] :
11037                                             second.twoMachinesColor[0] ;
11038
11039                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11040                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11041                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11042                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11043                 } else
11044                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11045                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11046                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11047                 } else
11048                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11049                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11050                 }
11051
11052                 // now verify win claims, but not in drop games, as we don't understand those yet
11053                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11054                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11055                     (result == WhiteWins && claimer == 'w' ||
11056                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11057                       if (appData.debugMode) {
11058                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11059                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11060                       }
11061                       if(result != trueResult) {
11062                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11063                               result = claimer == 'w' ? BlackWins : WhiteWins;
11064                               resultDetails = buf;
11065                       }
11066                 } else
11067                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11068                     && (forwardMostMove <= backwardMostMove ||
11069                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11070                         (claimer=='b')==(forwardMostMove&1))
11071                                                                                   ) {
11072                       /* [HGM] verify: draws that were not flagged are false claims */
11073                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11074                       result = claimer == 'w' ? BlackWins : WhiteWins;
11075                       resultDetails = buf;
11076                 }
11077                 /* (Claiming a loss is accepted no questions asked!) */
11078             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11079                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11080                 result = GameUnfinished;
11081                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11082             }
11083             /* [HGM] bare: don't allow bare King to win */
11084             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11085                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11086                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11087                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11088                && result != GameIsDrawn)
11089             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11090                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11091                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11092                         if(p >= 0 && p <= (int)WhiteKing) k++;
11093                 }
11094                 if (appData.debugMode) {
11095                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11096                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11097                 }
11098                 if(k <= 1) {
11099                         result = GameIsDrawn;
11100                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11101                         resultDetails = buf;
11102                 }
11103             }
11104         }
11105
11106
11107         if(serverMoves != NULL && !loadFlag) { char c = '=';
11108             if(result==WhiteWins) c = '+';
11109             if(result==BlackWins) c = '-';
11110             if(resultDetails != NULL)
11111                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11112         }
11113         if (resultDetails != NULL) {
11114             gameInfo.result = result;
11115             gameInfo.resultDetails = StrSave(resultDetails);
11116
11117             /* display last move only if game was not loaded from file */
11118             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11119                 DisplayMove(currentMove - 1);
11120
11121             if (forwardMostMove != 0) {
11122                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11123                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11124                                                                 ) {
11125                     if (*appData.saveGameFile != NULLCHAR) {
11126                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11127                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11128                         else
11129                         SaveGameToFile(appData.saveGameFile, TRUE);
11130                     } else if (appData.autoSaveGames) {
11131                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11132                     }
11133                     if (*appData.savePositionFile != NULLCHAR) {
11134                         SavePositionToFile(appData.savePositionFile);
11135                     }
11136                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11137                 }
11138             }
11139
11140             /* Tell program how game ended in case it is learning */
11141             /* [HGM] Moved this to after saving the PGN, just in case */
11142             /* engine died and we got here through time loss. In that */
11143             /* case we will get a fatal error writing the pipe, which */
11144             /* would otherwise lose us the PGN.                       */
11145             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11146             /* output during GameEnds should never be fatal anymore   */
11147             if (gameMode == MachinePlaysWhite ||
11148                 gameMode == MachinePlaysBlack ||
11149                 gameMode == TwoMachinesPlay ||
11150                 gameMode == IcsPlayingWhite ||
11151                 gameMode == IcsPlayingBlack ||
11152                 gameMode == BeginningOfGame) {
11153                 char buf[MSG_SIZ];
11154                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11155                         resultDetails);
11156                 if (first.pr != NoProc) {
11157                     SendToProgram(buf, &first);
11158                 }
11159                 if (second.pr != NoProc &&
11160                     gameMode == TwoMachinesPlay) {
11161                     SendToProgram(buf, &second);
11162                 }
11163             }
11164         }
11165
11166         if (appData.icsActive) {
11167             if (appData.quietPlay &&
11168                 (gameMode == IcsPlayingWhite ||
11169                  gameMode == IcsPlayingBlack)) {
11170                 SendToICS(ics_prefix);
11171                 SendToICS("set shout 1\n");
11172             }
11173             nextGameMode = IcsIdle;
11174             ics_user_moved = FALSE;
11175             /* clean up premove.  It's ugly when the game has ended and the
11176              * premove highlights are still on the board.
11177              */
11178             if (gotPremove) {
11179               gotPremove = FALSE;
11180               ClearPremoveHighlights();
11181               DrawPosition(FALSE, boards[currentMove]);
11182             }
11183             if (whosays == GE_ICS) {
11184                 switch (result) {
11185                 case WhiteWins:
11186                     if (gameMode == IcsPlayingWhite)
11187                         PlayIcsWinSound();
11188                     else if(gameMode == IcsPlayingBlack)
11189                         PlayIcsLossSound();
11190                     break;
11191                 case BlackWins:
11192                     if (gameMode == IcsPlayingBlack)
11193                         PlayIcsWinSound();
11194                     else if(gameMode == IcsPlayingWhite)
11195                         PlayIcsLossSound();
11196                     break;
11197                 case GameIsDrawn:
11198                     PlayIcsDrawSound();
11199                     break;
11200                 default:
11201                     PlayIcsUnfinishedSound();
11202                 }
11203             }
11204             if(appData.quitNext) { ExitEvent(0); return; }
11205         } else if (gameMode == EditGame ||
11206                    gameMode == PlayFromGameFile ||
11207                    gameMode == AnalyzeMode ||
11208                    gameMode == AnalyzeFile) {
11209             nextGameMode = gameMode;
11210         } else {
11211             nextGameMode = EndOfGame;
11212         }
11213         pausing = FALSE;
11214         ModeHighlight();
11215     } else {
11216         nextGameMode = gameMode;
11217     }
11218
11219     if (appData.noChessProgram) {
11220         gameMode = nextGameMode;
11221         ModeHighlight();
11222         endingGame = 0; /* [HGM] crash */
11223         return;
11224     }
11225
11226     if (first.reuse) {
11227         /* Put first chess program into idle state */
11228         if (first.pr != NoProc &&
11229             (gameMode == MachinePlaysWhite ||
11230              gameMode == MachinePlaysBlack ||
11231              gameMode == TwoMachinesPlay ||
11232              gameMode == IcsPlayingWhite ||
11233              gameMode == IcsPlayingBlack ||
11234              gameMode == BeginningOfGame)) {
11235             SendToProgram("force\n", &first);
11236             if (first.usePing) {
11237               char buf[MSG_SIZ];
11238               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11239               SendToProgram(buf, &first);
11240             }
11241         }
11242     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11243         /* Kill off first chess program */
11244         if (first.isr != NULL)
11245           RemoveInputSource(first.isr);
11246         first.isr = NULL;
11247
11248         if (first.pr != NoProc) {
11249             ExitAnalyzeMode();
11250             DoSleep( appData.delayBeforeQuit );
11251             SendToProgram("quit\n", &first);
11252             DoSleep( appData.delayAfterQuit );
11253             DestroyChildProcess(first.pr, first.useSigterm);
11254             first.reload = TRUE;
11255         }
11256         first.pr = NoProc;
11257     }
11258     if (second.reuse) {
11259         /* Put second chess program into idle state */
11260         if (second.pr != NoProc &&
11261             gameMode == TwoMachinesPlay) {
11262             SendToProgram("force\n", &second);
11263             if (second.usePing) {
11264               char buf[MSG_SIZ];
11265               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11266               SendToProgram(buf, &second);
11267             }
11268         }
11269     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11270         /* Kill off second chess program */
11271         if (second.isr != NULL)
11272           RemoveInputSource(second.isr);
11273         second.isr = NULL;
11274
11275         if (second.pr != NoProc) {
11276             DoSleep( appData.delayBeforeQuit );
11277             SendToProgram("quit\n", &second);
11278             DoSleep( appData.delayAfterQuit );
11279             DestroyChildProcess(second.pr, second.useSigterm);
11280             second.reload = TRUE;
11281         }
11282         second.pr = NoProc;
11283     }
11284
11285     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11286         char resChar = '=';
11287         switch (result) {
11288         case WhiteWins:
11289           resChar = '+';
11290           if (first.twoMachinesColor[0] == 'w') {
11291             first.matchWins++;
11292           } else {
11293             second.matchWins++;
11294           }
11295           break;
11296         case BlackWins:
11297           resChar = '-';
11298           if (first.twoMachinesColor[0] == 'b') {
11299             first.matchWins++;
11300           } else {
11301             second.matchWins++;
11302           }
11303           break;
11304         case GameUnfinished:
11305           resChar = ' ';
11306         default:
11307           break;
11308         }
11309
11310         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11311         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11312             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11313             ReserveGame(nextGame, resChar); // sets nextGame
11314             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11315             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11316         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11317
11318         if (nextGame <= appData.matchGames && !abortMatch) {
11319             gameMode = nextGameMode;
11320             matchGame = nextGame; // this will be overruled in tourney mode!
11321             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11322             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11323             endingGame = 0; /* [HGM] crash */
11324             return;
11325         } else {
11326             gameMode = nextGameMode;
11327             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11328                      first.tidy, second.tidy,
11329                      first.matchWins, second.matchWins,
11330                      appData.matchGames - (first.matchWins + second.matchWins));
11331             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11332             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11333             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11334             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11335                 first.twoMachinesColor = "black\n";
11336                 second.twoMachinesColor = "white\n";
11337             } else {
11338                 first.twoMachinesColor = "white\n";
11339                 second.twoMachinesColor = "black\n";
11340             }
11341         }
11342     }
11343     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11344         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11345       ExitAnalyzeMode();
11346     gameMode = nextGameMode;
11347     ModeHighlight();
11348     endingGame = 0;  /* [HGM] crash */
11349     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11350         if(matchMode == TRUE) { // match through command line: exit with or without popup
11351             if(ranking) {
11352                 ToNrEvent(forwardMostMove);
11353                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11354                 else ExitEvent(0);
11355             } else DisplayFatalError(buf, 0, 0);
11356         } else { // match through menu; just stop, with or without popup
11357             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11358             ModeHighlight();
11359             if(ranking){
11360                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11361             } else DisplayNote(buf);
11362       }
11363       if(ranking) free(ranking);
11364     }
11365 }
11366
11367 /* Assumes program was just initialized (initString sent).
11368    Leaves program in force mode. */
11369 void
11370 FeedMovesToProgram (ChessProgramState *cps, int upto)
11371 {
11372     int i;
11373
11374     if (appData.debugMode)
11375       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11376               startedFromSetupPosition ? "position and " : "",
11377               backwardMostMove, upto, cps->which);
11378     if(currentlyInitializedVariant != gameInfo.variant) {
11379       char buf[MSG_SIZ];
11380         // [HGM] variantswitch: make engine aware of new variant
11381         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11382                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11383                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11384         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11385         SendToProgram(buf, cps);
11386         currentlyInitializedVariant = gameInfo.variant;
11387     }
11388     SendToProgram("force\n", cps);
11389     if (startedFromSetupPosition) {
11390         SendBoard(cps, backwardMostMove);
11391     if (appData.debugMode) {
11392         fprintf(debugFP, "feedMoves\n");
11393     }
11394     }
11395     for (i = backwardMostMove; i < upto; i++) {
11396         SendMoveToProgram(i, cps);
11397     }
11398 }
11399
11400
11401 int
11402 ResurrectChessProgram ()
11403 {
11404      /* The chess program may have exited.
11405         If so, restart it and feed it all the moves made so far. */
11406     static int doInit = 0;
11407
11408     if (appData.noChessProgram) return 1;
11409
11410     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11411         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11412         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11413         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11414     } else {
11415         if (first.pr != NoProc) return 1;
11416         StartChessProgram(&first);
11417     }
11418     InitChessProgram(&first, FALSE);
11419     FeedMovesToProgram(&first, currentMove);
11420
11421     if (!first.sendTime) {
11422         /* can't tell gnuchess what its clock should read,
11423            so we bow to its notion. */
11424         ResetClocks();
11425         timeRemaining[0][currentMove] = whiteTimeRemaining;
11426         timeRemaining[1][currentMove] = blackTimeRemaining;
11427     }
11428
11429     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11430                 appData.icsEngineAnalyze) && first.analysisSupport) {
11431       SendToProgram("analyze\n", &first);
11432       first.analyzing = TRUE;
11433     }
11434     return 1;
11435 }
11436
11437 /*
11438  * Button procedures
11439  */
11440 void
11441 Reset (int redraw, int init)
11442 {
11443     int i;
11444
11445     if (appData.debugMode) {
11446         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11447                 redraw, init, gameMode);
11448     }
11449     CleanupTail(); // [HGM] vari: delete any stored variations
11450     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11451     pausing = pauseExamInvalid = FALSE;
11452     startedFromSetupPosition = blackPlaysFirst = FALSE;
11453     firstMove = TRUE;
11454     whiteFlag = blackFlag = FALSE;
11455     userOfferedDraw = FALSE;
11456     hintRequested = bookRequested = FALSE;
11457     first.maybeThinking = FALSE;
11458     second.maybeThinking = FALSE;
11459     first.bookSuspend = FALSE; // [HGM] book
11460     second.bookSuspend = FALSE;
11461     thinkOutput[0] = NULLCHAR;
11462     lastHint[0] = NULLCHAR;
11463     ClearGameInfo(&gameInfo);
11464     gameInfo.variant = StringToVariant(appData.variant);
11465     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11466     ics_user_moved = ics_clock_paused = FALSE;
11467     ics_getting_history = H_FALSE;
11468     ics_gamenum = -1;
11469     white_holding[0] = black_holding[0] = NULLCHAR;
11470     ClearProgramStats();
11471     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11472
11473     ResetFrontEnd();
11474     ClearHighlights();
11475     flipView = appData.flipView;
11476     ClearPremoveHighlights();
11477     gotPremove = FALSE;
11478     alarmSounded = FALSE;
11479     killX = killY = -1; // [HGM] lion
11480
11481     GameEnds(EndOfFile, NULL, GE_PLAYER);
11482     if(appData.serverMovesName != NULL) {
11483         /* [HGM] prepare to make moves file for broadcasting */
11484         clock_t t = clock();
11485         if(serverMoves != NULL) fclose(serverMoves);
11486         serverMoves = fopen(appData.serverMovesName, "r");
11487         if(serverMoves != NULL) {
11488             fclose(serverMoves);
11489             /* delay 15 sec before overwriting, so all clients can see end */
11490             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11491         }
11492         serverMoves = fopen(appData.serverMovesName, "w");
11493     }
11494
11495     ExitAnalyzeMode();
11496     gameMode = BeginningOfGame;
11497     ModeHighlight();
11498     if(appData.icsActive) gameInfo.variant = VariantNormal;
11499     currentMove = forwardMostMove = backwardMostMove = 0;
11500     MarkTargetSquares(1);
11501     InitPosition(redraw);
11502     for (i = 0; i < MAX_MOVES; i++) {
11503         if (commentList[i] != NULL) {
11504             free(commentList[i]);
11505             commentList[i] = NULL;
11506         }
11507     }
11508     ResetClocks();
11509     timeRemaining[0][0] = whiteTimeRemaining;
11510     timeRemaining[1][0] = blackTimeRemaining;
11511
11512     if (first.pr == NoProc) {
11513         StartChessProgram(&first);
11514     }
11515     if (init) {
11516             InitChessProgram(&first, startedFromSetupPosition);
11517     }
11518     DisplayTitle("");
11519     DisplayMessage("", "");
11520     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11521     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11522     ClearMap();        // [HGM] exclude: invalidate map
11523 }
11524
11525 void
11526 AutoPlayGameLoop ()
11527 {
11528     for (;;) {
11529         if (!AutoPlayOneMove())
11530           return;
11531         if (matchMode || appData.timeDelay == 0)
11532           continue;
11533         if (appData.timeDelay < 0)
11534           return;
11535         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11536         break;
11537     }
11538 }
11539
11540 void
11541 AnalyzeNextGame()
11542 {
11543     ReloadGame(1); // next game
11544 }
11545
11546 int
11547 AutoPlayOneMove ()
11548 {
11549     int fromX, fromY, toX, toY;
11550
11551     if (appData.debugMode) {
11552       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11553     }
11554
11555     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11556       return FALSE;
11557
11558     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11559       pvInfoList[currentMove].depth = programStats.depth;
11560       pvInfoList[currentMove].score = programStats.score;
11561       pvInfoList[currentMove].time  = 0;
11562       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11563       else { // append analysis of final position as comment
11564         char buf[MSG_SIZ];
11565         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11566         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11567       }
11568       programStats.depth = 0;
11569     }
11570
11571     if (currentMove >= forwardMostMove) {
11572       if(gameMode == AnalyzeFile) {
11573           if(appData.loadGameIndex == -1) {
11574             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11575           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11576           } else {
11577           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11578         }
11579       }
11580 //      gameMode = EndOfGame;
11581 //      ModeHighlight();
11582
11583       /* [AS] Clear current move marker at the end of a game */
11584       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11585
11586       return FALSE;
11587     }
11588
11589     toX = moveList[currentMove][2] - AAA;
11590     toY = moveList[currentMove][3] - ONE;
11591
11592     if (moveList[currentMove][1] == '@') {
11593         if (appData.highlightLastMove) {
11594             SetHighlights(-1, -1, toX, toY);
11595         }
11596     } else {
11597         fromX = moveList[currentMove][0] - AAA;
11598         fromY = moveList[currentMove][1] - ONE;
11599
11600         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11601
11602         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11603
11604         if (appData.highlightLastMove) {
11605             SetHighlights(fromX, fromY, toX, toY);
11606         }
11607     }
11608     DisplayMove(currentMove);
11609     SendMoveToProgram(currentMove++, &first);
11610     DisplayBothClocks();
11611     DrawPosition(FALSE, boards[currentMove]);
11612     // [HGM] PV info: always display, routine tests if empty
11613     DisplayComment(currentMove - 1, commentList[currentMove]);
11614     return TRUE;
11615 }
11616
11617
11618 int
11619 LoadGameOneMove (ChessMove readAhead)
11620 {
11621     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11622     char promoChar = NULLCHAR;
11623     ChessMove moveType;
11624     char move[MSG_SIZ];
11625     char *p, *q;
11626
11627     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11628         gameMode != AnalyzeMode && gameMode != Training) {
11629         gameFileFP = NULL;
11630         return FALSE;
11631     }
11632
11633     yyboardindex = forwardMostMove;
11634     if (readAhead != EndOfFile) {
11635       moveType = readAhead;
11636     } else {
11637       if (gameFileFP == NULL)
11638           return FALSE;
11639       moveType = (ChessMove) Myylex();
11640     }
11641
11642     done = FALSE;
11643     switch (moveType) {
11644       case Comment:
11645         if (appData.debugMode)
11646           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11647         p = yy_text;
11648
11649         /* append the comment but don't display it */
11650         AppendComment(currentMove, p, FALSE);
11651         return TRUE;
11652
11653       case WhiteCapturesEnPassant:
11654       case BlackCapturesEnPassant:
11655       case WhitePromotion:
11656       case BlackPromotion:
11657       case WhiteNonPromotion:
11658       case BlackNonPromotion:
11659       case NormalMove:
11660       case FirstLeg:
11661       case WhiteKingSideCastle:
11662       case WhiteQueenSideCastle:
11663       case BlackKingSideCastle:
11664       case BlackQueenSideCastle:
11665       case WhiteKingSideCastleWild:
11666       case WhiteQueenSideCastleWild:
11667       case BlackKingSideCastleWild:
11668       case BlackQueenSideCastleWild:
11669       /* PUSH Fabien */
11670       case WhiteHSideCastleFR:
11671       case WhiteASideCastleFR:
11672       case BlackHSideCastleFR:
11673       case BlackASideCastleFR:
11674       /* POP Fabien */
11675         if (appData.debugMode)
11676           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11677         fromX = currentMoveString[0] - AAA;
11678         fromY = currentMoveString[1] - ONE;
11679         toX = currentMoveString[2] - AAA;
11680         toY = currentMoveString[3] - ONE;
11681         promoChar = currentMoveString[4];
11682         if(promoChar == ';') promoChar = NULLCHAR;
11683         break;
11684
11685       case WhiteDrop:
11686       case BlackDrop:
11687         if (appData.debugMode)
11688           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11689         fromX = moveType == WhiteDrop ?
11690           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11691         (int) CharToPiece(ToLower(currentMoveString[0]));
11692         fromY = DROP_RANK;
11693         toX = currentMoveString[2] - AAA;
11694         toY = currentMoveString[3] - ONE;
11695         break;
11696
11697       case WhiteWins:
11698       case BlackWins:
11699       case GameIsDrawn:
11700       case GameUnfinished:
11701         if (appData.debugMode)
11702           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11703         p = strchr(yy_text, '{');
11704         if (p == NULL) p = strchr(yy_text, '(');
11705         if (p == NULL) {
11706             p = yy_text;
11707             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11708         } else {
11709             q = strchr(p, *p == '{' ? '}' : ')');
11710             if (q != NULL) *q = NULLCHAR;
11711             p++;
11712         }
11713         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11714         GameEnds(moveType, p, GE_FILE);
11715         done = TRUE;
11716         if (cmailMsgLoaded) {
11717             ClearHighlights();
11718             flipView = WhiteOnMove(currentMove);
11719             if (moveType == GameUnfinished) flipView = !flipView;
11720             if (appData.debugMode)
11721               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11722         }
11723         break;
11724
11725       case EndOfFile:
11726         if (appData.debugMode)
11727           fprintf(debugFP, "Parser hit end of file\n");
11728         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11729           case MT_NONE:
11730           case MT_CHECK:
11731             break;
11732           case MT_CHECKMATE:
11733           case MT_STAINMATE:
11734             if (WhiteOnMove(currentMove)) {
11735                 GameEnds(BlackWins, "Black mates", GE_FILE);
11736             } else {
11737                 GameEnds(WhiteWins, "White mates", GE_FILE);
11738             }
11739             break;
11740           case MT_STALEMATE:
11741             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11742             break;
11743         }
11744         done = TRUE;
11745         break;
11746
11747       case MoveNumberOne:
11748         if (lastLoadGameStart == GNUChessGame) {
11749             /* GNUChessGames have numbers, but they aren't move numbers */
11750             if (appData.debugMode)
11751               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11752                       yy_text, (int) moveType);
11753             return LoadGameOneMove(EndOfFile); /* tail recursion */
11754         }
11755         /* else fall thru */
11756
11757       case XBoardGame:
11758       case GNUChessGame:
11759       case PGNTag:
11760         /* Reached start of next game in file */
11761         if (appData.debugMode)
11762           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11763         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11764           case MT_NONE:
11765           case MT_CHECK:
11766             break;
11767           case MT_CHECKMATE:
11768           case MT_STAINMATE:
11769             if (WhiteOnMove(currentMove)) {
11770                 GameEnds(BlackWins, "Black mates", GE_FILE);
11771             } else {
11772                 GameEnds(WhiteWins, "White mates", GE_FILE);
11773             }
11774             break;
11775           case MT_STALEMATE:
11776             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11777             break;
11778         }
11779         done = TRUE;
11780         break;
11781
11782       case PositionDiagram:     /* should not happen; ignore */
11783       case ElapsedTime:         /* ignore */
11784       case NAG:                 /* ignore */
11785         if (appData.debugMode)
11786           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11787                   yy_text, (int) moveType);
11788         return LoadGameOneMove(EndOfFile); /* tail recursion */
11789
11790       case IllegalMove:
11791         if (appData.testLegality) {
11792             if (appData.debugMode)
11793               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11794             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11795                     (forwardMostMove / 2) + 1,
11796                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11797             DisplayError(move, 0);
11798             done = TRUE;
11799         } else {
11800             if (appData.debugMode)
11801               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11802                       yy_text, currentMoveString);
11803             fromX = currentMoveString[0] - AAA;
11804             fromY = currentMoveString[1] - ONE;
11805             toX = currentMoveString[2] - AAA;
11806             toY = currentMoveString[3] - ONE;
11807             promoChar = currentMoveString[4];
11808         }
11809         break;
11810
11811       case AmbiguousMove:
11812         if (appData.debugMode)
11813           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11814         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11815                 (forwardMostMove / 2) + 1,
11816                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11817         DisplayError(move, 0);
11818         done = TRUE;
11819         break;
11820
11821       default:
11822       case ImpossibleMove:
11823         if (appData.debugMode)
11824           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11825         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11826                 (forwardMostMove / 2) + 1,
11827                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11828         DisplayError(move, 0);
11829         done = TRUE;
11830         break;
11831     }
11832
11833     if (done) {
11834         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11835             DrawPosition(FALSE, boards[currentMove]);
11836             DisplayBothClocks();
11837             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11838               DisplayComment(currentMove - 1, commentList[currentMove]);
11839         }
11840         (void) StopLoadGameTimer();
11841         gameFileFP = NULL;
11842         cmailOldMove = forwardMostMove;
11843         return FALSE;
11844     } else {
11845         /* currentMoveString is set as a side-effect of yylex */
11846
11847         thinkOutput[0] = NULLCHAR;
11848         MakeMove(fromX, fromY, toX, toY, promoChar);
11849         killX = killY = -1; // [HGM] lion: used up
11850         currentMove = forwardMostMove;
11851         return TRUE;
11852     }
11853 }
11854
11855 /* Load the nth game from the given file */
11856 int
11857 LoadGameFromFile (char *filename, int n, char *title, int useList)
11858 {
11859     FILE *f;
11860     char buf[MSG_SIZ];
11861
11862     if (strcmp(filename, "-") == 0) {
11863         f = stdin;
11864         title = "stdin";
11865     } else {
11866         f = fopen(filename, "rb");
11867         if (f == NULL) {
11868           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11869             DisplayError(buf, errno);
11870             return FALSE;
11871         }
11872     }
11873     if (fseek(f, 0, 0) == -1) {
11874         /* f is not seekable; probably a pipe */
11875         useList = FALSE;
11876     }
11877     if (useList && n == 0) {
11878         int error = GameListBuild(f);
11879         if (error) {
11880             DisplayError(_("Cannot build game list"), error);
11881         } else if (!ListEmpty(&gameList) &&
11882                    ((ListGame *) gameList.tailPred)->number > 1) {
11883             GameListPopUp(f, title);
11884             return TRUE;
11885         }
11886         GameListDestroy();
11887         n = 1;
11888     }
11889     if (n == 0) n = 1;
11890     return LoadGame(f, n, title, FALSE);
11891 }
11892
11893
11894 void
11895 MakeRegisteredMove ()
11896 {
11897     int fromX, fromY, toX, toY;
11898     char promoChar;
11899     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11900         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11901           case CMAIL_MOVE:
11902           case CMAIL_DRAW:
11903             if (appData.debugMode)
11904               fprintf(debugFP, "Restoring %s for game %d\n",
11905                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11906
11907             thinkOutput[0] = NULLCHAR;
11908             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11909             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11910             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11911             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11912             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11913             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11914             MakeMove(fromX, fromY, toX, toY, promoChar);
11915             ShowMove(fromX, fromY, toX, toY);
11916
11917             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11918               case MT_NONE:
11919               case MT_CHECK:
11920                 break;
11921
11922               case MT_CHECKMATE:
11923               case MT_STAINMATE:
11924                 if (WhiteOnMove(currentMove)) {
11925                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11926                 } else {
11927                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11928                 }
11929                 break;
11930
11931               case MT_STALEMATE:
11932                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11933                 break;
11934             }
11935
11936             break;
11937
11938           case CMAIL_RESIGN:
11939             if (WhiteOnMove(currentMove)) {
11940                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11941             } else {
11942                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11943             }
11944             break;
11945
11946           case CMAIL_ACCEPT:
11947             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11948             break;
11949
11950           default:
11951             break;
11952         }
11953     }
11954
11955     return;
11956 }
11957
11958 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11959 int
11960 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11961 {
11962     int retVal;
11963
11964     if (gameNumber > nCmailGames) {
11965         DisplayError(_("No more games in this message"), 0);
11966         return FALSE;
11967     }
11968     if (f == lastLoadGameFP) {
11969         int offset = gameNumber - lastLoadGameNumber;
11970         if (offset == 0) {
11971             cmailMsg[0] = NULLCHAR;
11972             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11973                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11974                 nCmailMovesRegistered--;
11975             }
11976             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11977             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11978                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11979             }
11980         } else {
11981             if (! RegisterMove()) return FALSE;
11982         }
11983     }
11984
11985     retVal = LoadGame(f, gameNumber, title, useList);
11986
11987     /* Make move registered during previous look at this game, if any */
11988     MakeRegisteredMove();
11989
11990     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11991         commentList[currentMove]
11992           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11993         DisplayComment(currentMove - 1, commentList[currentMove]);
11994     }
11995
11996     return retVal;
11997 }
11998
11999 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12000 int
12001 ReloadGame (int offset)
12002 {
12003     int gameNumber = lastLoadGameNumber + offset;
12004     if (lastLoadGameFP == NULL) {
12005         DisplayError(_("No game has been loaded yet"), 0);
12006         return FALSE;
12007     }
12008     if (gameNumber <= 0) {
12009         DisplayError(_("Can't back up any further"), 0);
12010         return FALSE;
12011     }
12012     if (cmailMsgLoaded) {
12013         return CmailLoadGame(lastLoadGameFP, gameNumber,
12014                              lastLoadGameTitle, lastLoadGameUseList);
12015     } else {
12016         return LoadGame(lastLoadGameFP, gameNumber,
12017                         lastLoadGameTitle, lastLoadGameUseList);
12018     }
12019 }
12020
12021 int keys[EmptySquare+1];
12022
12023 int
12024 PositionMatches (Board b1, Board b2)
12025 {
12026     int r, f, sum=0;
12027     switch(appData.searchMode) {
12028         case 1: return CompareWithRights(b1, b2);
12029         case 2:
12030             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12031                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12032             }
12033             return TRUE;
12034         case 3:
12035             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12036               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12037                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12038             }
12039             return sum==0;
12040         case 4:
12041             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12042                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12043             }
12044             return sum==0;
12045     }
12046     return TRUE;
12047 }
12048
12049 #define Q_PROMO  4
12050 #define Q_EP     3
12051 #define Q_BCASTL 2
12052 #define Q_WCASTL 1
12053
12054 int pieceList[256], quickBoard[256];
12055 ChessSquare pieceType[256] = { EmptySquare };
12056 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12057 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12058 int soughtTotal, turn;
12059 Boolean epOK, flipSearch;
12060
12061 typedef struct {
12062     unsigned char piece, to;
12063 } Move;
12064
12065 #define DSIZE (250000)
12066
12067 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12068 Move *moveDatabase = initialSpace;
12069 unsigned int movePtr, dataSize = DSIZE;
12070
12071 int
12072 MakePieceList (Board board, int *counts)
12073 {
12074     int r, f, n=Q_PROMO, total=0;
12075     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12076     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12077         int sq = f + (r<<4);
12078         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12079             quickBoard[sq] = ++n;
12080             pieceList[n] = sq;
12081             pieceType[n] = board[r][f];
12082             counts[board[r][f]]++;
12083             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12084             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12085             total++;
12086         }
12087     }
12088     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12089     return total;
12090 }
12091
12092 void
12093 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12094 {
12095     int sq = fromX + (fromY<<4);
12096     int piece = quickBoard[sq];
12097     quickBoard[sq] = 0;
12098     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12099     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12100         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12101         moveDatabase[movePtr++].piece = Q_WCASTL;
12102         quickBoard[sq] = piece;
12103         piece = quickBoard[from]; quickBoard[from] = 0;
12104         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12105     } else
12106     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12107         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12108         moveDatabase[movePtr++].piece = Q_BCASTL;
12109         quickBoard[sq] = piece;
12110         piece = quickBoard[from]; quickBoard[from] = 0;
12111         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12112     } else
12113     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12114         quickBoard[(fromY<<4)+toX] = 0;
12115         moveDatabase[movePtr].piece = Q_EP;
12116         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12117         moveDatabase[movePtr].to = sq;
12118     } else
12119     if(promoPiece != pieceType[piece]) {
12120         moveDatabase[movePtr++].piece = Q_PROMO;
12121         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12122     }
12123     moveDatabase[movePtr].piece = piece;
12124     quickBoard[sq] = piece;
12125     movePtr++;
12126 }
12127
12128 int
12129 PackGame (Board board)
12130 {
12131     Move *newSpace = NULL;
12132     moveDatabase[movePtr].piece = 0; // terminate previous game
12133     if(movePtr > dataSize) {
12134         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12135         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12136         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12137         if(newSpace) {
12138             int i;
12139             Move *p = moveDatabase, *q = newSpace;
12140             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12141             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12142             moveDatabase = newSpace;
12143         } else { // calloc failed, we must be out of memory. Too bad...
12144             dataSize = 0; // prevent calloc events for all subsequent games
12145             return 0;     // and signal this one isn't cached
12146         }
12147     }
12148     movePtr++;
12149     MakePieceList(board, counts);
12150     return movePtr;
12151 }
12152
12153 int
12154 QuickCompare (Board board, int *minCounts, int *maxCounts)
12155 {   // compare according to search mode
12156     int r, f;
12157     switch(appData.searchMode)
12158     {
12159       case 1: // exact position match
12160         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12161         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12163         }
12164         break;
12165       case 2: // can have extra material on empty squares
12166         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12167             if(board[r][f] == EmptySquare) continue;
12168             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12169         }
12170         break;
12171       case 3: // material with exact Pawn structure
12172         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12173             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12174             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12175         } // fall through to material comparison
12176       case 4: // exact material
12177         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12178         break;
12179       case 6: // material range with given imbalance
12180         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12181         // fall through to range comparison
12182       case 5: // material range
12183         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12184     }
12185     return TRUE;
12186 }
12187
12188 int
12189 QuickScan (Board board, Move *move)
12190 {   // reconstruct game,and compare all positions in it
12191     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12192     do {
12193         int piece = move->piece;
12194         int to = move->to, from = pieceList[piece];
12195         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12196           if(!piece) return -1;
12197           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12198             piece = (++move)->piece;
12199             from = pieceList[piece];
12200             counts[pieceType[piece]]--;
12201             pieceType[piece] = (ChessSquare) move->to;
12202             counts[move->to]++;
12203           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12204             counts[pieceType[quickBoard[to]]]--;
12205             quickBoard[to] = 0; total--;
12206             move++;
12207             continue;
12208           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12209             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12210             from  = pieceList[piece]; // so this must be King
12211             quickBoard[from] = 0;
12212             pieceList[piece] = to;
12213             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12214             quickBoard[from] = 0; // rook
12215             quickBoard[to] = piece;
12216             to = move->to; piece = move->piece;
12217             goto aftercastle;
12218           }
12219         }
12220         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12221         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12222         quickBoard[from] = 0;
12223       aftercastle:
12224         quickBoard[to] = piece;
12225         pieceList[piece] = to;
12226         cnt++; turn ^= 3;
12227         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12228            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12229            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12230                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12231           ) {
12232             static int lastCounts[EmptySquare+1];
12233             int i;
12234             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12235             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12236         } else stretch = 0;
12237         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12238         move++;
12239     } while(1);
12240 }
12241
12242 void
12243 InitSearch ()
12244 {
12245     int r, f;
12246     flipSearch = FALSE;
12247     CopyBoard(soughtBoard, boards[currentMove]);
12248     soughtTotal = MakePieceList(soughtBoard, maxSought);
12249     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12250     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12251     CopyBoard(reverseBoard, boards[currentMove]);
12252     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12253         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12254         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12255         reverseBoard[r][f] = piece;
12256     }
12257     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12258     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12259     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12260                  || (boards[currentMove][CASTLING][2] == NoRights ||
12261                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12262                  && (boards[currentMove][CASTLING][5] == NoRights ||
12263                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12264       ) {
12265         flipSearch = TRUE;
12266         CopyBoard(flipBoard, soughtBoard);
12267         CopyBoard(rotateBoard, reverseBoard);
12268         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12269             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12270             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12271         }
12272     }
12273     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12274     if(appData.searchMode >= 5) {
12275         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12276         MakePieceList(soughtBoard, minSought);
12277         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12278     }
12279     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12280         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12281 }
12282
12283 GameInfo dummyInfo;
12284 static int creatingBook;
12285
12286 int
12287 GameContainsPosition (FILE *f, ListGame *lg)
12288 {
12289     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12290     int fromX, fromY, toX, toY;
12291     char promoChar;
12292     static int initDone=FALSE;
12293
12294     // weed out games based on numerical tag comparison
12295     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12296     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12297     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12298     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12299     if(!initDone) {
12300         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12301         initDone = TRUE;
12302     }
12303     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12304     else CopyBoard(boards[scratch], initialPosition); // default start position
12305     if(lg->moves) {
12306         turn = btm + 1;
12307         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12308         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12309     }
12310     if(btm) plyNr++;
12311     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12312     fseek(f, lg->offset, 0);
12313     yynewfile(f);
12314     while(1) {
12315         yyboardindex = scratch;
12316         quickFlag = plyNr+1;
12317         next = Myylex();
12318         quickFlag = 0;
12319         switch(next) {
12320             case PGNTag:
12321                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12322             default:
12323                 continue;
12324
12325             case XBoardGame:
12326             case GNUChessGame:
12327                 if(plyNr) return -1; // after we have seen moves, this is for new game
12328               continue;
12329
12330             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12331             case ImpossibleMove:
12332             case WhiteWins: // game ends here with these four
12333             case BlackWins:
12334             case GameIsDrawn:
12335             case GameUnfinished:
12336                 return -1;
12337
12338             case IllegalMove:
12339                 if(appData.testLegality) return -1;
12340             case WhiteCapturesEnPassant:
12341             case BlackCapturesEnPassant:
12342             case WhitePromotion:
12343             case BlackPromotion:
12344             case WhiteNonPromotion:
12345             case BlackNonPromotion:
12346             case NormalMove:
12347             case FirstLeg:
12348             case WhiteKingSideCastle:
12349             case WhiteQueenSideCastle:
12350             case BlackKingSideCastle:
12351             case BlackQueenSideCastle:
12352             case WhiteKingSideCastleWild:
12353             case WhiteQueenSideCastleWild:
12354             case BlackKingSideCastleWild:
12355             case BlackQueenSideCastleWild:
12356             case WhiteHSideCastleFR:
12357             case WhiteASideCastleFR:
12358             case BlackHSideCastleFR:
12359             case BlackASideCastleFR:
12360                 fromX = currentMoveString[0] - AAA;
12361                 fromY = currentMoveString[1] - ONE;
12362                 toX = currentMoveString[2] - AAA;
12363                 toY = currentMoveString[3] - ONE;
12364                 promoChar = currentMoveString[4];
12365                 break;
12366             case WhiteDrop:
12367             case BlackDrop:
12368                 fromX = next == WhiteDrop ?
12369                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12370                   (int) CharToPiece(ToLower(currentMoveString[0]));
12371                 fromY = DROP_RANK;
12372                 toX = currentMoveString[2] - AAA;
12373                 toY = currentMoveString[3] - ONE;
12374                 promoChar = 0;
12375                 break;
12376         }
12377         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12378         plyNr++;
12379         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12380         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12381         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12382         if(appData.findMirror) {
12383             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12384             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12385         }
12386     }
12387 }
12388
12389 /* Load the nth game from open file f */
12390 int
12391 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12392 {
12393     ChessMove cm;
12394     char buf[MSG_SIZ];
12395     int gn = gameNumber;
12396     ListGame *lg = NULL;
12397     int numPGNTags = 0;
12398     int err, pos = -1;
12399     GameMode oldGameMode;
12400     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12401
12402     if (appData.debugMode)
12403         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12404
12405     if (gameMode == Training )
12406         SetTrainingModeOff();
12407
12408     oldGameMode = gameMode;
12409     if (gameMode != BeginningOfGame) {
12410       Reset(FALSE, TRUE);
12411     }
12412     killX = killY = -1; // [HGM] lion: in case we did not Reset
12413
12414     gameFileFP = f;
12415     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12416         fclose(lastLoadGameFP);
12417     }
12418
12419     if (useList) {
12420         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12421
12422         if (lg) {
12423             fseek(f, lg->offset, 0);
12424             GameListHighlight(gameNumber);
12425             pos = lg->position;
12426             gn = 1;
12427         }
12428         else {
12429             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12430               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12431             else
12432             DisplayError(_("Game number out of range"), 0);
12433             return FALSE;
12434         }
12435     } else {
12436         GameListDestroy();
12437         if (fseek(f, 0, 0) == -1) {
12438             if (f == lastLoadGameFP ?
12439                 gameNumber == lastLoadGameNumber + 1 :
12440                 gameNumber == 1) {
12441                 gn = 1;
12442             } else {
12443                 DisplayError(_("Can't seek on game file"), 0);
12444                 return FALSE;
12445             }
12446         }
12447     }
12448     lastLoadGameFP = f;
12449     lastLoadGameNumber = gameNumber;
12450     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12451     lastLoadGameUseList = useList;
12452
12453     yynewfile(f);
12454
12455     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12456       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12457                 lg->gameInfo.black);
12458             DisplayTitle(buf);
12459     } else if (*title != NULLCHAR) {
12460         if (gameNumber > 1) {
12461           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12462             DisplayTitle(buf);
12463         } else {
12464             DisplayTitle(title);
12465         }
12466     }
12467
12468     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12469         gameMode = PlayFromGameFile;
12470         ModeHighlight();
12471     }
12472
12473     currentMove = forwardMostMove = backwardMostMove = 0;
12474     CopyBoard(boards[0], initialPosition);
12475     StopClocks();
12476
12477     /*
12478      * Skip the first gn-1 games in the file.
12479      * Also skip over anything that precedes an identifiable
12480      * start of game marker, to avoid being confused by
12481      * garbage at the start of the file.  Currently
12482      * recognized start of game markers are the move number "1",
12483      * the pattern "gnuchess .* game", the pattern
12484      * "^[#;%] [^ ]* game file", and a PGN tag block.
12485      * A game that starts with one of the latter two patterns
12486      * will also have a move number 1, possibly
12487      * following a position diagram.
12488      * 5-4-02: Let's try being more lenient and allowing a game to
12489      * start with an unnumbered move.  Does that break anything?
12490      */
12491     cm = lastLoadGameStart = EndOfFile;
12492     while (gn > 0) {
12493         yyboardindex = forwardMostMove;
12494         cm = (ChessMove) Myylex();
12495         switch (cm) {
12496           case EndOfFile:
12497             if (cmailMsgLoaded) {
12498                 nCmailGames = CMAIL_MAX_GAMES - gn;
12499             } else {
12500                 Reset(TRUE, TRUE);
12501                 DisplayError(_("Game not found in file"), 0);
12502             }
12503             return FALSE;
12504
12505           case GNUChessGame:
12506           case XBoardGame:
12507             gn--;
12508             lastLoadGameStart = cm;
12509             break;
12510
12511           case MoveNumberOne:
12512             switch (lastLoadGameStart) {
12513               case GNUChessGame:
12514               case XBoardGame:
12515               case PGNTag:
12516                 break;
12517               case MoveNumberOne:
12518               case EndOfFile:
12519                 gn--;           /* count this game */
12520                 lastLoadGameStart = cm;
12521                 break;
12522               default:
12523                 /* impossible */
12524                 break;
12525             }
12526             break;
12527
12528           case PGNTag:
12529             switch (lastLoadGameStart) {
12530               case GNUChessGame:
12531               case PGNTag:
12532               case MoveNumberOne:
12533               case EndOfFile:
12534                 gn--;           /* count this game */
12535                 lastLoadGameStart = cm;
12536                 break;
12537               case XBoardGame:
12538                 lastLoadGameStart = cm; /* game counted already */
12539                 break;
12540               default:
12541                 /* impossible */
12542                 break;
12543             }
12544             if (gn > 0) {
12545                 do {
12546                     yyboardindex = forwardMostMove;
12547                     cm = (ChessMove) Myylex();
12548                 } while (cm == PGNTag || cm == Comment);
12549             }
12550             break;
12551
12552           case WhiteWins:
12553           case BlackWins:
12554           case GameIsDrawn:
12555             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12556                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12557                     != CMAIL_OLD_RESULT) {
12558                     nCmailResults ++ ;
12559                     cmailResult[  CMAIL_MAX_GAMES
12560                                 - gn - 1] = CMAIL_OLD_RESULT;
12561                 }
12562             }
12563             break;
12564
12565           case NormalMove:
12566           case FirstLeg:
12567             /* Only a NormalMove can be at the start of a game
12568              * without a position diagram. */
12569             if (lastLoadGameStart == EndOfFile ) {
12570               gn--;
12571               lastLoadGameStart = MoveNumberOne;
12572             }
12573             break;
12574
12575           default:
12576             break;
12577         }
12578     }
12579
12580     if (appData.debugMode)
12581       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12582
12583     if (cm == XBoardGame) {
12584         /* Skip any header junk before position diagram and/or move 1 */
12585         for (;;) {
12586             yyboardindex = forwardMostMove;
12587             cm = (ChessMove) Myylex();
12588
12589             if (cm == EndOfFile ||
12590                 cm == GNUChessGame || cm == XBoardGame) {
12591                 /* Empty game; pretend end-of-file and handle later */
12592                 cm = EndOfFile;
12593                 break;
12594             }
12595
12596             if (cm == MoveNumberOne || cm == PositionDiagram ||
12597                 cm == PGNTag || cm == Comment)
12598               break;
12599         }
12600     } else if (cm == GNUChessGame) {
12601         if (gameInfo.event != NULL) {
12602             free(gameInfo.event);
12603         }
12604         gameInfo.event = StrSave(yy_text);
12605     }
12606
12607     startedFromSetupPosition = FALSE;
12608     while (cm == PGNTag) {
12609         if (appData.debugMode)
12610           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12611         err = ParsePGNTag(yy_text, &gameInfo);
12612         if (!err) numPGNTags++;
12613
12614         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12615         if(gameInfo.variant != oldVariant) {
12616             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12617             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12618             InitPosition(TRUE);
12619             oldVariant = gameInfo.variant;
12620             if (appData.debugMode)
12621               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12622         }
12623
12624
12625         if (gameInfo.fen != NULL) {
12626           Board initial_position;
12627           startedFromSetupPosition = TRUE;
12628           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12629             Reset(TRUE, TRUE);
12630             DisplayError(_("Bad FEN position in file"), 0);
12631             return FALSE;
12632           }
12633           CopyBoard(boards[0], initial_position);
12634           if (blackPlaysFirst) {
12635             currentMove = forwardMostMove = backwardMostMove = 1;
12636             CopyBoard(boards[1], initial_position);
12637             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12638             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12639             timeRemaining[0][1] = whiteTimeRemaining;
12640             timeRemaining[1][1] = blackTimeRemaining;
12641             if (commentList[0] != NULL) {
12642               commentList[1] = commentList[0];
12643               commentList[0] = NULL;
12644             }
12645           } else {
12646             currentMove = forwardMostMove = backwardMostMove = 0;
12647           }
12648           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12649           {   int i;
12650               initialRulePlies = FENrulePlies;
12651               for( i=0; i< nrCastlingRights; i++ )
12652                   initialRights[i] = initial_position[CASTLING][i];
12653           }
12654           yyboardindex = forwardMostMove;
12655           free(gameInfo.fen);
12656           gameInfo.fen = NULL;
12657         }
12658
12659         yyboardindex = forwardMostMove;
12660         cm = (ChessMove) Myylex();
12661
12662         /* Handle comments interspersed among the tags */
12663         while (cm == Comment) {
12664             char *p;
12665             if (appData.debugMode)
12666               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12667             p = yy_text;
12668             AppendComment(currentMove, p, FALSE);
12669             yyboardindex = forwardMostMove;
12670             cm = (ChessMove) Myylex();
12671         }
12672     }
12673
12674     /* don't rely on existence of Event tag since if game was
12675      * pasted from clipboard the Event tag may not exist
12676      */
12677     if (numPGNTags > 0){
12678         char *tags;
12679         if (gameInfo.variant == VariantNormal) {
12680           VariantClass v = StringToVariant(gameInfo.event);
12681           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12682           if(v < VariantShogi) gameInfo.variant = v;
12683         }
12684         if (!matchMode) {
12685           if( appData.autoDisplayTags ) {
12686             tags = PGNTags(&gameInfo);
12687             TagsPopUp(tags, CmailMsg());
12688             free(tags);
12689           }
12690         }
12691     } else {
12692         /* Make something up, but don't display it now */
12693         SetGameInfo();
12694         TagsPopDown();
12695     }
12696
12697     if (cm == PositionDiagram) {
12698         int i, j;
12699         char *p;
12700         Board initial_position;
12701
12702         if (appData.debugMode)
12703           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12704
12705         if (!startedFromSetupPosition) {
12706             p = yy_text;
12707             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12708               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12709                 switch (*p) {
12710                   case '{':
12711                   case '[':
12712                   case '-':
12713                   case ' ':
12714                   case '\t':
12715                   case '\n':
12716                   case '\r':
12717                     break;
12718                   default:
12719                     initial_position[i][j++] = CharToPiece(*p);
12720                     break;
12721                 }
12722             while (*p == ' ' || *p == '\t' ||
12723                    *p == '\n' || *p == '\r') p++;
12724
12725             if (strncmp(p, "black", strlen("black"))==0)
12726               blackPlaysFirst = TRUE;
12727             else
12728               blackPlaysFirst = FALSE;
12729             startedFromSetupPosition = TRUE;
12730
12731             CopyBoard(boards[0], initial_position);
12732             if (blackPlaysFirst) {
12733                 currentMove = forwardMostMove = backwardMostMove = 1;
12734                 CopyBoard(boards[1], initial_position);
12735                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12736                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12737                 timeRemaining[0][1] = whiteTimeRemaining;
12738                 timeRemaining[1][1] = blackTimeRemaining;
12739                 if (commentList[0] != NULL) {
12740                     commentList[1] = commentList[0];
12741                     commentList[0] = NULL;
12742                 }
12743             } else {
12744                 currentMove = forwardMostMove = backwardMostMove = 0;
12745             }
12746         }
12747         yyboardindex = forwardMostMove;
12748         cm = (ChessMove) Myylex();
12749     }
12750
12751   if(!creatingBook) {
12752     if (first.pr == NoProc) {
12753         StartChessProgram(&first);
12754     }
12755     InitChessProgram(&first, FALSE);
12756     SendToProgram("force\n", &first);
12757     if (startedFromSetupPosition) {
12758         SendBoard(&first, forwardMostMove);
12759     if (appData.debugMode) {
12760         fprintf(debugFP, "Load Game\n");
12761     }
12762         DisplayBothClocks();
12763     }
12764   }
12765
12766     /* [HGM] server: flag to write setup moves in broadcast file as one */
12767     loadFlag = appData.suppressLoadMoves;
12768
12769     while (cm == Comment) {
12770         char *p;
12771         if (appData.debugMode)
12772           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12773         p = yy_text;
12774         AppendComment(currentMove, p, FALSE);
12775         yyboardindex = forwardMostMove;
12776         cm = (ChessMove) Myylex();
12777     }
12778
12779     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12780         cm == WhiteWins || cm == BlackWins ||
12781         cm == GameIsDrawn || cm == GameUnfinished) {
12782         DisplayMessage("", _("No moves in game"));
12783         if (cmailMsgLoaded) {
12784             if (appData.debugMode)
12785               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12786             ClearHighlights();
12787             flipView = FALSE;
12788         }
12789         DrawPosition(FALSE, boards[currentMove]);
12790         DisplayBothClocks();
12791         gameMode = EditGame;
12792         ModeHighlight();
12793         gameFileFP = NULL;
12794         cmailOldMove = 0;
12795         return TRUE;
12796     }
12797
12798     // [HGM] PV info: routine tests if comment empty
12799     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12800         DisplayComment(currentMove - 1, commentList[currentMove]);
12801     }
12802     if (!matchMode && appData.timeDelay != 0)
12803       DrawPosition(FALSE, boards[currentMove]);
12804
12805     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12806       programStats.ok_to_send = 1;
12807     }
12808
12809     /* if the first token after the PGN tags is a move
12810      * and not move number 1, retrieve it from the parser
12811      */
12812     if (cm != MoveNumberOne)
12813         LoadGameOneMove(cm);
12814
12815     /* load the remaining moves from the file */
12816     while (LoadGameOneMove(EndOfFile)) {
12817       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12818       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12819     }
12820
12821     /* rewind to the start of the game */
12822     currentMove = backwardMostMove;
12823
12824     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12825
12826     if (oldGameMode == AnalyzeFile) {
12827       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12828       AnalyzeFileEvent();
12829     } else
12830     if (oldGameMode == AnalyzeMode) {
12831       AnalyzeFileEvent();
12832     }
12833
12834     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12835         long int w, b; // [HGM] adjourn: restore saved clock times
12836         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12837         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12838             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12839             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12840         }
12841     }
12842
12843     if(creatingBook) return TRUE;
12844     if (!matchMode && pos > 0) {
12845         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12846     } else
12847     if (matchMode || appData.timeDelay == 0) {
12848       ToEndEvent();
12849     } else if (appData.timeDelay > 0) {
12850       AutoPlayGameLoop();
12851     }
12852
12853     if (appData.debugMode)
12854         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12855
12856     loadFlag = 0; /* [HGM] true game starts */
12857     return TRUE;
12858 }
12859
12860 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12861 int
12862 ReloadPosition (int offset)
12863 {
12864     int positionNumber = lastLoadPositionNumber + offset;
12865     if (lastLoadPositionFP == NULL) {
12866         DisplayError(_("No position has been loaded yet"), 0);
12867         return FALSE;
12868     }
12869     if (positionNumber <= 0) {
12870         DisplayError(_("Can't back up any further"), 0);
12871         return FALSE;
12872     }
12873     return LoadPosition(lastLoadPositionFP, positionNumber,
12874                         lastLoadPositionTitle);
12875 }
12876
12877 /* Load the nth position from the given file */
12878 int
12879 LoadPositionFromFile (char *filename, int n, char *title)
12880 {
12881     FILE *f;
12882     char buf[MSG_SIZ];
12883
12884     if (strcmp(filename, "-") == 0) {
12885         return LoadPosition(stdin, n, "stdin");
12886     } else {
12887         f = fopen(filename, "rb");
12888         if (f == NULL) {
12889             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12890             DisplayError(buf, errno);
12891             return FALSE;
12892         } else {
12893             return LoadPosition(f, n, title);
12894         }
12895     }
12896 }
12897
12898 /* Load the nth position from the given open file, and close it */
12899 int
12900 LoadPosition (FILE *f, int positionNumber, char *title)
12901 {
12902     char *p, line[MSG_SIZ];
12903     Board initial_position;
12904     int i, j, fenMode, pn;
12905
12906     if (gameMode == Training )
12907         SetTrainingModeOff();
12908
12909     if (gameMode != BeginningOfGame) {
12910         Reset(FALSE, TRUE);
12911     }
12912     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12913         fclose(lastLoadPositionFP);
12914     }
12915     if (positionNumber == 0) positionNumber = 1;
12916     lastLoadPositionFP = f;
12917     lastLoadPositionNumber = positionNumber;
12918     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12919     if (first.pr == NoProc && !appData.noChessProgram) {
12920       StartChessProgram(&first);
12921       InitChessProgram(&first, FALSE);
12922     }
12923     pn = positionNumber;
12924     if (positionNumber < 0) {
12925         /* Negative position number means to seek to that byte offset */
12926         if (fseek(f, -positionNumber, 0) == -1) {
12927             DisplayError(_("Can't seek on position file"), 0);
12928             return FALSE;
12929         };
12930         pn = 1;
12931     } else {
12932         if (fseek(f, 0, 0) == -1) {
12933             if (f == lastLoadPositionFP ?
12934                 positionNumber == lastLoadPositionNumber + 1 :
12935                 positionNumber == 1) {
12936                 pn = 1;
12937             } else {
12938                 DisplayError(_("Can't seek on position file"), 0);
12939                 return FALSE;
12940             }
12941         }
12942     }
12943     /* See if this file is FEN or old-style xboard */
12944     if (fgets(line, MSG_SIZ, f) == NULL) {
12945         DisplayError(_("Position not found in file"), 0);
12946         return FALSE;
12947     }
12948     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12949     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12950
12951     if (pn >= 2) {
12952         if (fenMode || line[0] == '#') pn--;
12953         while (pn > 0) {
12954             /* skip positions before number pn */
12955             if (fgets(line, MSG_SIZ, f) == NULL) {
12956                 Reset(TRUE, TRUE);
12957                 DisplayError(_("Position not found in file"), 0);
12958                 return FALSE;
12959             }
12960             if (fenMode || line[0] == '#') pn--;
12961         }
12962     }
12963
12964     if (fenMode) {
12965         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12966             DisplayError(_("Bad FEN position in file"), 0);
12967             return FALSE;
12968         }
12969     } else {
12970         (void) fgets(line, MSG_SIZ, f);
12971         (void) fgets(line, MSG_SIZ, f);
12972
12973         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12974             (void) fgets(line, MSG_SIZ, f);
12975             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12976                 if (*p == ' ')
12977                   continue;
12978                 initial_position[i][j++] = CharToPiece(*p);
12979             }
12980         }
12981
12982         blackPlaysFirst = FALSE;
12983         if (!feof(f)) {
12984             (void) fgets(line, MSG_SIZ, f);
12985             if (strncmp(line, "black", strlen("black"))==0)
12986               blackPlaysFirst = TRUE;
12987         }
12988     }
12989     startedFromSetupPosition = TRUE;
12990
12991     CopyBoard(boards[0], initial_position);
12992     if (blackPlaysFirst) {
12993         currentMove = forwardMostMove = backwardMostMove = 1;
12994         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12995         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12996         CopyBoard(boards[1], initial_position);
12997         DisplayMessage("", _("Black to play"));
12998     } else {
12999         currentMove = forwardMostMove = backwardMostMove = 0;
13000         DisplayMessage("", _("White to play"));
13001     }
13002     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13003     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13004         SendToProgram("force\n", &first);
13005         SendBoard(&first, forwardMostMove);
13006     }
13007     if (appData.debugMode) {
13008 int i, j;
13009   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13010   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13011         fprintf(debugFP, "Load Position\n");
13012     }
13013
13014     if (positionNumber > 1) {
13015       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13016         DisplayTitle(line);
13017     } else {
13018         DisplayTitle(title);
13019     }
13020     gameMode = EditGame;
13021     ModeHighlight();
13022     ResetClocks();
13023     timeRemaining[0][1] = whiteTimeRemaining;
13024     timeRemaining[1][1] = blackTimeRemaining;
13025     DrawPosition(FALSE, boards[currentMove]);
13026
13027     return TRUE;
13028 }
13029
13030
13031 void
13032 CopyPlayerNameIntoFileName (char **dest, char *src)
13033 {
13034     while (*src != NULLCHAR && *src != ',') {
13035         if (*src == ' ') {
13036             *(*dest)++ = '_';
13037             src++;
13038         } else {
13039             *(*dest)++ = *src++;
13040         }
13041     }
13042 }
13043
13044 char *
13045 DefaultFileName (char *ext)
13046 {
13047     static char def[MSG_SIZ];
13048     char *p;
13049
13050     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13051         p = def;
13052         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13053         *p++ = '-';
13054         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13055         *p++ = '.';
13056         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13057     } else {
13058         def[0] = NULLCHAR;
13059     }
13060     return def;
13061 }
13062
13063 /* Save the current game to the given file */
13064 int
13065 SaveGameToFile (char *filename, int append)
13066 {
13067     FILE *f;
13068     char buf[MSG_SIZ];
13069     int result, i, t,tot=0;
13070
13071     if (strcmp(filename, "-") == 0) {
13072         return SaveGame(stdout, 0, NULL);
13073     } else {
13074         for(i=0; i<10; i++) { // upto 10 tries
13075              f = fopen(filename, append ? "a" : "w");
13076              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13077              if(f || errno != 13) break;
13078              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13079              tot += t;
13080         }
13081         if (f == NULL) {
13082             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13083             DisplayError(buf, errno);
13084             return FALSE;
13085         } else {
13086             safeStrCpy(buf, lastMsg, MSG_SIZ);
13087             DisplayMessage(_("Waiting for access to save file"), "");
13088             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13089             DisplayMessage(_("Saving game"), "");
13090             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13091             result = SaveGame(f, 0, NULL);
13092             DisplayMessage(buf, "");
13093             return result;
13094         }
13095     }
13096 }
13097
13098 char *
13099 SavePart (char *str)
13100 {
13101     static char buf[MSG_SIZ];
13102     char *p;
13103
13104     p = strchr(str, ' ');
13105     if (p == NULL) return str;
13106     strncpy(buf, str, p - str);
13107     buf[p - str] = NULLCHAR;
13108     return buf;
13109 }
13110
13111 #define PGN_MAX_LINE 75
13112
13113 #define PGN_SIDE_WHITE  0
13114 #define PGN_SIDE_BLACK  1
13115
13116 static int
13117 FindFirstMoveOutOfBook (int side)
13118 {
13119     int result = -1;
13120
13121     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13122         int index = backwardMostMove;
13123         int has_book_hit = 0;
13124
13125         if( (index % 2) != side ) {
13126             index++;
13127         }
13128
13129         while( index < forwardMostMove ) {
13130             /* Check to see if engine is in book */
13131             int depth = pvInfoList[index].depth;
13132             int score = pvInfoList[index].score;
13133             int in_book = 0;
13134
13135             if( depth <= 2 ) {
13136                 in_book = 1;
13137             }
13138             else if( score == 0 && depth == 63 ) {
13139                 in_book = 1; /* Zappa */
13140             }
13141             else if( score == 2 && depth == 99 ) {
13142                 in_book = 1; /* Abrok */
13143             }
13144
13145             has_book_hit += in_book;
13146
13147             if( ! in_book ) {
13148                 result = index;
13149
13150                 break;
13151             }
13152
13153             index += 2;
13154         }
13155     }
13156
13157     return result;
13158 }
13159
13160 void
13161 GetOutOfBookInfo (char * buf)
13162 {
13163     int oob[2];
13164     int i;
13165     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13166
13167     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13168     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13169
13170     *buf = '\0';
13171
13172     if( oob[0] >= 0 || oob[1] >= 0 ) {
13173         for( i=0; i<2; i++ ) {
13174             int idx = oob[i];
13175
13176             if( idx >= 0 ) {
13177                 if( i > 0 && oob[0] >= 0 ) {
13178                     strcat( buf, "   " );
13179                 }
13180
13181                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13182                 sprintf( buf+strlen(buf), "%s%.2f",
13183                     pvInfoList[idx].score >= 0 ? "+" : "",
13184                     pvInfoList[idx].score / 100.0 );
13185             }
13186         }
13187     }
13188 }
13189
13190 /* Save game in PGN style and close the file */
13191 int
13192 SaveGamePGN (FILE *f)
13193 {
13194     int i, offset, linelen, newblock;
13195 //    char *movetext;
13196     char numtext[32];
13197     int movelen, numlen, blank;
13198     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13199
13200     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13201
13202     PrintPGNTags(f, &gameInfo);
13203
13204     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13205
13206     if (backwardMostMove > 0 || startedFromSetupPosition) {
13207         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13208         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13209         fprintf(f, "\n{--------------\n");
13210         PrintPosition(f, backwardMostMove);
13211         fprintf(f, "--------------}\n");
13212         free(fen);
13213     }
13214     else {
13215         /* [AS] Out of book annotation */
13216         if( appData.saveOutOfBookInfo ) {
13217             char buf[64];
13218
13219             GetOutOfBookInfo( buf );
13220
13221             if( buf[0] != '\0' ) {
13222                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13223             }
13224         }
13225
13226         fprintf(f, "\n");
13227     }
13228
13229     i = backwardMostMove;
13230     linelen = 0;
13231     newblock = TRUE;
13232
13233     while (i < forwardMostMove) {
13234         /* Print comments preceding this move */
13235         if (commentList[i] != NULL) {
13236             if (linelen > 0) fprintf(f, "\n");
13237             fprintf(f, "%s", commentList[i]);
13238             linelen = 0;
13239             newblock = TRUE;
13240         }
13241
13242         /* Format move number */
13243         if ((i % 2) == 0)
13244           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13245         else
13246           if (newblock)
13247             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13248           else
13249             numtext[0] = NULLCHAR;
13250
13251         numlen = strlen(numtext);
13252         newblock = FALSE;
13253
13254         /* Print move number */
13255         blank = linelen > 0 && numlen > 0;
13256         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13257             fprintf(f, "\n");
13258             linelen = 0;
13259             blank = 0;
13260         }
13261         if (blank) {
13262             fprintf(f, " ");
13263             linelen++;
13264         }
13265         fprintf(f, "%s", numtext);
13266         linelen += numlen;
13267
13268         /* Get move */
13269         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13270         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13271
13272         /* Print move */
13273         blank = linelen > 0 && movelen > 0;
13274         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13275             fprintf(f, "\n");
13276             linelen = 0;
13277             blank = 0;
13278         }
13279         if (blank) {
13280             fprintf(f, " ");
13281             linelen++;
13282         }
13283         fprintf(f, "%s", move_buffer);
13284         linelen += movelen;
13285
13286         /* [AS] Add PV info if present */
13287         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13288             /* [HGM] add time */
13289             char buf[MSG_SIZ]; int seconds;
13290
13291             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13292
13293             if( seconds <= 0)
13294               buf[0] = 0;
13295             else
13296               if( seconds < 30 )
13297                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13298               else
13299                 {
13300                   seconds = (seconds + 4)/10; // round to full seconds
13301                   if( seconds < 60 )
13302                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13303                   else
13304                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13305                 }
13306
13307             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13308                       pvInfoList[i].score >= 0 ? "+" : "",
13309                       pvInfoList[i].score / 100.0,
13310                       pvInfoList[i].depth,
13311                       buf );
13312
13313             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13314
13315             /* Print score/depth */
13316             blank = linelen > 0 && movelen > 0;
13317             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13318                 fprintf(f, "\n");
13319                 linelen = 0;
13320                 blank = 0;
13321             }
13322             if (blank) {
13323                 fprintf(f, " ");
13324                 linelen++;
13325             }
13326             fprintf(f, "%s", move_buffer);
13327             linelen += movelen;
13328         }
13329
13330         i++;
13331     }
13332
13333     /* Start a new line */
13334     if (linelen > 0) fprintf(f, "\n");
13335
13336     /* Print comments after last move */
13337     if (commentList[i] != NULL) {
13338         fprintf(f, "%s\n", commentList[i]);
13339     }
13340
13341     /* Print result */
13342     if (gameInfo.resultDetails != NULL &&
13343         gameInfo.resultDetails[0] != NULLCHAR) {
13344         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13345         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13346            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13347             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13348         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13349     } else {
13350         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13351     }
13352
13353     fclose(f);
13354     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13355     return TRUE;
13356 }
13357
13358 /* Save game in old style and close the file */
13359 int
13360 SaveGameOldStyle (FILE *f)
13361 {
13362     int i, offset;
13363     time_t tm;
13364
13365     tm = time((time_t *) NULL);
13366
13367     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13368     PrintOpponents(f);
13369
13370     if (backwardMostMove > 0 || startedFromSetupPosition) {
13371         fprintf(f, "\n[--------------\n");
13372         PrintPosition(f, backwardMostMove);
13373         fprintf(f, "--------------]\n");
13374     } else {
13375         fprintf(f, "\n");
13376     }
13377
13378     i = backwardMostMove;
13379     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13380
13381     while (i < forwardMostMove) {
13382         if (commentList[i] != NULL) {
13383             fprintf(f, "[%s]\n", commentList[i]);
13384         }
13385
13386         if ((i % 2) == 1) {
13387             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13388             i++;
13389         } else {
13390             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13391             i++;
13392             if (commentList[i] != NULL) {
13393                 fprintf(f, "\n");
13394                 continue;
13395             }
13396             if (i >= forwardMostMove) {
13397                 fprintf(f, "\n");
13398                 break;
13399             }
13400             fprintf(f, "%s\n", parseList[i]);
13401             i++;
13402         }
13403     }
13404
13405     if (commentList[i] != NULL) {
13406         fprintf(f, "[%s]\n", commentList[i]);
13407     }
13408
13409     /* This isn't really the old style, but it's close enough */
13410     if (gameInfo.resultDetails != NULL &&
13411         gameInfo.resultDetails[0] != NULLCHAR) {
13412         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13413                 gameInfo.resultDetails);
13414     } else {
13415         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13416     }
13417
13418     fclose(f);
13419     return TRUE;
13420 }
13421
13422 /* Save the current game to open file f and close the file */
13423 int
13424 SaveGame (FILE *f, int dummy, char *dummy2)
13425 {
13426     if (gameMode == EditPosition) EditPositionDone(TRUE);
13427     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13428     if (appData.oldSaveStyle)
13429       return SaveGameOldStyle(f);
13430     else
13431       return SaveGamePGN(f);
13432 }
13433
13434 /* Save the current position to the given file */
13435 int
13436 SavePositionToFile (char *filename)
13437 {
13438     FILE *f;
13439     char buf[MSG_SIZ];
13440
13441     if (strcmp(filename, "-") == 0) {
13442         return SavePosition(stdout, 0, NULL);
13443     } else {
13444         f = fopen(filename, "a");
13445         if (f == NULL) {
13446             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13447             DisplayError(buf, errno);
13448             return FALSE;
13449         } else {
13450             safeStrCpy(buf, lastMsg, MSG_SIZ);
13451             DisplayMessage(_("Waiting for access to save file"), "");
13452             flock(fileno(f), LOCK_EX); // [HGM] lock
13453             DisplayMessage(_("Saving position"), "");
13454             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13455             SavePosition(f, 0, NULL);
13456             DisplayMessage(buf, "");
13457             return TRUE;
13458         }
13459     }
13460 }
13461
13462 /* Save the current position to the given open file and close the file */
13463 int
13464 SavePosition (FILE *f, int dummy, char *dummy2)
13465 {
13466     time_t tm;
13467     char *fen;
13468
13469     if (gameMode == EditPosition) EditPositionDone(TRUE);
13470     if (appData.oldSaveStyle) {
13471         tm = time((time_t *) NULL);
13472
13473         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13474         PrintOpponents(f);
13475         fprintf(f, "[--------------\n");
13476         PrintPosition(f, currentMove);
13477         fprintf(f, "--------------]\n");
13478     } else {
13479         fen = PositionToFEN(currentMove, NULL, 1);
13480         fprintf(f, "%s\n", fen);
13481         free(fen);
13482     }
13483     fclose(f);
13484     return TRUE;
13485 }
13486
13487 void
13488 ReloadCmailMsgEvent (int unregister)
13489 {
13490 #if !WIN32
13491     static char *inFilename = NULL;
13492     static char *outFilename;
13493     int i;
13494     struct stat inbuf, outbuf;
13495     int status;
13496
13497     /* Any registered moves are unregistered if unregister is set, */
13498     /* i.e. invoked by the signal handler */
13499     if (unregister) {
13500         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13501             cmailMoveRegistered[i] = FALSE;
13502             if (cmailCommentList[i] != NULL) {
13503                 free(cmailCommentList[i]);
13504                 cmailCommentList[i] = NULL;
13505             }
13506         }
13507         nCmailMovesRegistered = 0;
13508     }
13509
13510     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13511         cmailResult[i] = CMAIL_NOT_RESULT;
13512     }
13513     nCmailResults = 0;
13514
13515     if (inFilename == NULL) {
13516         /* Because the filenames are static they only get malloced once  */
13517         /* and they never get freed                                      */
13518         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13519         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13520
13521         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13522         sprintf(outFilename, "%s.out", appData.cmailGameName);
13523     }
13524
13525     status = stat(outFilename, &outbuf);
13526     if (status < 0) {
13527         cmailMailedMove = FALSE;
13528     } else {
13529         status = stat(inFilename, &inbuf);
13530         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13531     }
13532
13533     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13534        counts the games, notes how each one terminated, etc.
13535
13536        It would be nice to remove this kludge and instead gather all
13537        the information while building the game list.  (And to keep it
13538        in the game list nodes instead of having a bunch of fixed-size
13539        parallel arrays.)  Note this will require getting each game's
13540        termination from the PGN tags, as the game list builder does
13541        not process the game moves.  --mann
13542        */
13543     cmailMsgLoaded = TRUE;
13544     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13545
13546     /* Load first game in the file or popup game menu */
13547     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13548
13549 #endif /* !WIN32 */
13550     return;
13551 }
13552
13553 int
13554 RegisterMove ()
13555 {
13556     FILE *f;
13557     char string[MSG_SIZ];
13558
13559     if (   cmailMailedMove
13560         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13561         return TRUE;            /* Allow free viewing  */
13562     }
13563
13564     /* Unregister move to ensure that we don't leave RegisterMove        */
13565     /* with the move registered when the conditions for registering no   */
13566     /* longer hold                                                       */
13567     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13568         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13569         nCmailMovesRegistered --;
13570
13571         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13572           {
13573               free(cmailCommentList[lastLoadGameNumber - 1]);
13574               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13575           }
13576     }
13577
13578     if (cmailOldMove == -1) {
13579         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13580         return FALSE;
13581     }
13582
13583     if (currentMove > cmailOldMove + 1) {
13584         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13585         return FALSE;
13586     }
13587
13588     if (currentMove < cmailOldMove) {
13589         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13590         return FALSE;
13591     }
13592
13593     if (forwardMostMove > currentMove) {
13594         /* Silently truncate extra moves */
13595         TruncateGame();
13596     }
13597
13598     if (   (currentMove == cmailOldMove + 1)
13599         || (   (currentMove == cmailOldMove)
13600             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13601                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13602         if (gameInfo.result != GameUnfinished) {
13603             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13604         }
13605
13606         if (commentList[currentMove] != NULL) {
13607             cmailCommentList[lastLoadGameNumber - 1]
13608               = StrSave(commentList[currentMove]);
13609         }
13610         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13611
13612         if (appData.debugMode)
13613           fprintf(debugFP, "Saving %s for game %d\n",
13614                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13615
13616         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13617
13618         f = fopen(string, "w");
13619         if (appData.oldSaveStyle) {
13620             SaveGameOldStyle(f); /* also closes the file */
13621
13622             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13623             f = fopen(string, "w");
13624             SavePosition(f, 0, NULL); /* also closes the file */
13625         } else {
13626             fprintf(f, "{--------------\n");
13627             PrintPosition(f, currentMove);
13628             fprintf(f, "--------------}\n\n");
13629
13630             SaveGame(f, 0, NULL); /* also closes the file*/
13631         }
13632
13633         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13634         nCmailMovesRegistered ++;
13635     } else if (nCmailGames == 1) {
13636         DisplayError(_("You have not made a move yet"), 0);
13637         return FALSE;
13638     }
13639
13640     return TRUE;
13641 }
13642
13643 void
13644 MailMoveEvent ()
13645 {
13646 #if !WIN32
13647     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13648     FILE *commandOutput;
13649     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13650     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13651     int nBuffers;
13652     int i;
13653     int archived;
13654     char *arcDir;
13655
13656     if (! cmailMsgLoaded) {
13657         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13658         return;
13659     }
13660
13661     if (nCmailGames == nCmailResults) {
13662         DisplayError(_("No unfinished games"), 0);
13663         return;
13664     }
13665
13666 #if CMAIL_PROHIBIT_REMAIL
13667     if (cmailMailedMove) {
13668       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);
13669         DisplayError(msg, 0);
13670         return;
13671     }
13672 #endif
13673
13674     if (! (cmailMailedMove || RegisterMove())) return;
13675
13676     if (   cmailMailedMove
13677         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13678       snprintf(string, MSG_SIZ, partCommandString,
13679                appData.debugMode ? " -v" : "", appData.cmailGameName);
13680         commandOutput = popen(string, "r");
13681
13682         if (commandOutput == NULL) {
13683             DisplayError(_("Failed to invoke cmail"), 0);
13684         } else {
13685             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13686                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13687             }
13688             if (nBuffers > 1) {
13689                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13690                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13691                 nBytes = MSG_SIZ - 1;
13692             } else {
13693                 (void) memcpy(msg, buffer, nBytes);
13694             }
13695             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13696
13697             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13698                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13699
13700                 archived = TRUE;
13701                 for (i = 0; i < nCmailGames; i ++) {
13702                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13703                         archived = FALSE;
13704                     }
13705                 }
13706                 if (   archived
13707                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13708                         != NULL)) {
13709                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13710                            arcDir,
13711                            appData.cmailGameName,
13712                            gameInfo.date);
13713                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13714                     cmailMsgLoaded = FALSE;
13715                 }
13716             }
13717
13718             DisplayInformation(msg);
13719             pclose(commandOutput);
13720         }
13721     } else {
13722         if ((*cmailMsg) != '\0') {
13723             DisplayInformation(cmailMsg);
13724         }
13725     }
13726
13727     return;
13728 #endif /* !WIN32 */
13729 }
13730
13731 char *
13732 CmailMsg ()
13733 {
13734 #if WIN32
13735     return NULL;
13736 #else
13737     int  prependComma = 0;
13738     char number[5];
13739     char string[MSG_SIZ];       /* Space for game-list */
13740     int  i;
13741
13742     if (!cmailMsgLoaded) return "";
13743
13744     if (cmailMailedMove) {
13745       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13746     } else {
13747         /* Create a list of games left */
13748       snprintf(string, MSG_SIZ, "[");
13749         for (i = 0; i < nCmailGames; i ++) {
13750             if (! (   cmailMoveRegistered[i]
13751                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13752                 if (prependComma) {
13753                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13754                 } else {
13755                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13756                     prependComma = 1;
13757                 }
13758
13759                 strcat(string, number);
13760             }
13761         }
13762         strcat(string, "]");
13763
13764         if (nCmailMovesRegistered + nCmailResults == 0) {
13765             switch (nCmailGames) {
13766               case 1:
13767                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13768                 break;
13769
13770               case 2:
13771                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13772                 break;
13773
13774               default:
13775                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13776                          nCmailGames);
13777                 break;
13778             }
13779         } else {
13780             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13781               case 1:
13782                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13783                          string);
13784                 break;
13785
13786               case 0:
13787                 if (nCmailResults == nCmailGames) {
13788                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13789                 } else {
13790                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13791                 }
13792                 break;
13793
13794               default:
13795                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13796                          string);
13797             }
13798         }
13799     }
13800     return cmailMsg;
13801 #endif /* WIN32 */
13802 }
13803
13804 void
13805 ResetGameEvent ()
13806 {
13807     if (gameMode == Training)
13808       SetTrainingModeOff();
13809
13810     Reset(TRUE, TRUE);
13811     cmailMsgLoaded = FALSE;
13812     if (appData.icsActive) {
13813       SendToICS(ics_prefix);
13814       SendToICS("refresh\n");
13815     }
13816 }
13817
13818 void
13819 ExitEvent (int status)
13820 {
13821     exiting++;
13822     if (exiting > 2) {
13823       /* Give up on clean exit */
13824       exit(status);
13825     }
13826     if (exiting > 1) {
13827       /* Keep trying for clean exit */
13828       return;
13829     }
13830
13831     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13832
13833     if (telnetISR != NULL) {
13834       RemoveInputSource(telnetISR);
13835     }
13836     if (icsPR != NoProc) {
13837       DestroyChildProcess(icsPR, TRUE);
13838     }
13839
13840     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13841     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13842
13843     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13844     /* make sure this other one finishes before killing it!                  */
13845     if(endingGame) { int count = 0;
13846         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13847         while(endingGame && count++ < 10) DoSleep(1);
13848         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13849     }
13850
13851     /* Kill off chess programs */
13852     if (first.pr != NoProc) {
13853         ExitAnalyzeMode();
13854
13855         DoSleep( appData.delayBeforeQuit );
13856         SendToProgram("quit\n", &first);
13857         DoSleep( appData.delayAfterQuit );
13858         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13859     }
13860     if (second.pr != NoProc) {
13861         DoSleep( appData.delayBeforeQuit );
13862         SendToProgram("quit\n", &second);
13863         DoSleep( appData.delayAfterQuit );
13864         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13865     }
13866     if (first.isr != NULL) {
13867         RemoveInputSource(first.isr);
13868     }
13869     if (second.isr != NULL) {
13870         RemoveInputSource(second.isr);
13871     }
13872
13873     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13874     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13875
13876     ShutDownFrontEnd();
13877     exit(status);
13878 }
13879
13880 void
13881 PauseEngine (ChessProgramState *cps)
13882 {
13883     SendToProgram("pause\n", cps);
13884     cps->pause = 2;
13885 }
13886
13887 void
13888 UnPauseEngine (ChessProgramState *cps)
13889 {
13890     SendToProgram("resume\n", cps);
13891     cps->pause = 1;
13892 }
13893
13894 void
13895 PauseEvent ()
13896 {
13897     if (appData.debugMode)
13898         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13899     if (pausing) {
13900         pausing = FALSE;
13901         ModeHighlight();
13902         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13903             StartClocks();
13904             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13905                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13906                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13907             }
13908             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13909             HandleMachineMove(stashedInputMove, stalledEngine);
13910             stalledEngine = NULL;
13911             return;
13912         }
13913         if (gameMode == MachinePlaysWhite ||
13914             gameMode == TwoMachinesPlay   ||
13915             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13916             if(first.pause)  UnPauseEngine(&first);
13917             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13918             if(second.pause) UnPauseEngine(&second);
13919             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13920             StartClocks();
13921         } else {
13922             DisplayBothClocks();
13923         }
13924         if (gameMode == PlayFromGameFile) {
13925             if (appData.timeDelay >= 0)
13926                 AutoPlayGameLoop();
13927         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13928             Reset(FALSE, TRUE);
13929             SendToICS(ics_prefix);
13930             SendToICS("refresh\n");
13931         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13932             ForwardInner(forwardMostMove);
13933         }
13934         pauseExamInvalid = FALSE;
13935     } else {
13936         switch (gameMode) {
13937           default:
13938             return;
13939           case IcsExamining:
13940             pauseExamForwardMostMove = forwardMostMove;
13941             pauseExamInvalid = FALSE;
13942             /* fall through */
13943           case IcsObserving:
13944           case IcsPlayingWhite:
13945           case IcsPlayingBlack:
13946             pausing = TRUE;
13947             ModeHighlight();
13948             return;
13949           case PlayFromGameFile:
13950             (void) StopLoadGameTimer();
13951             pausing = TRUE;
13952             ModeHighlight();
13953             break;
13954           case BeginningOfGame:
13955             if (appData.icsActive) return;
13956             /* else fall through */
13957           case MachinePlaysWhite:
13958           case MachinePlaysBlack:
13959           case TwoMachinesPlay:
13960             if (forwardMostMove == 0)
13961               return;           /* don't pause if no one has moved */
13962             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13963                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13964                 if(onMove->pause) {           // thinking engine can be paused
13965                     PauseEngine(onMove);      // do it
13966                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13967                         PauseEngine(onMove->other);
13968                     else
13969                         SendToProgram("easy\n", onMove->other);
13970                     StopClocks();
13971                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13972             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13973                 if(first.pause) {
13974                     PauseEngine(&first);
13975                     StopClocks();
13976                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13977             } else { // human on move, pause pondering by either method
13978                 if(first.pause)
13979                     PauseEngine(&first);
13980                 else if(appData.ponderNextMove)
13981                     SendToProgram("easy\n", &first);
13982                 StopClocks();
13983             }
13984             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13985           case AnalyzeMode:
13986             pausing = TRUE;
13987             ModeHighlight();
13988             break;
13989         }
13990     }
13991 }
13992
13993 void
13994 EditCommentEvent ()
13995 {
13996     char title[MSG_SIZ];
13997
13998     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13999       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14000     } else {
14001       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14002                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14003                parseList[currentMove - 1]);
14004     }
14005
14006     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14007 }
14008
14009
14010 void
14011 EditTagsEvent ()
14012 {
14013     char *tags = PGNTags(&gameInfo);
14014     bookUp = FALSE;
14015     EditTagsPopUp(tags, NULL);
14016     free(tags);
14017 }
14018
14019 void
14020 ToggleSecond ()
14021 {
14022   if(second.analyzing) {
14023     SendToProgram("exit\n", &second);
14024     second.analyzing = FALSE;
14025   } else {
14026     if (second.pr == NoProc) StartChessProgram(&second);
14027     InitChessProgram(&second, FALSE);
14028     FeedMovesToProgram(&second, currentMove);
14029
14030     SendToProgram("analyze\n", &second);
14031     second.analyzing = TRUE;
14032   }
14033 }
14034
14035 /* Toggle ShowThinking */
14036 void
14037 ToggleShowThinking()
14038 {
14039   appData.showThinking = !appData.showThinking;
14040   ShowThinkingEvent();
14041 }
14042
14043 int
14044 AnalyzeModeEvent ()
14045 {
14046     char buf[MSG_SIZ];
14047
14048     if (!first.analysisSupport) {
14049       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14050       DisplayError(buf, 0);
14051       return 0;
14052     }
14053     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14054     if (appData.icsActive) {
14055         if (gameMode != IcsObserving) {
14056           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14057             DisplayError(buf, 0);
14058             /* secure check */
14059             if (appData.icsEngineAnalyze) {
14060                 if (appData.debugMode)
14061                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14062                 ExitAnalyzeMode();
14063                 ModeHighlight();
14064             }
14065             return 0;
14066         }
14067         /* if enable, user wants to disable icsEngineAnalyze */
14068         if (appData.icsEngineAnalyze) {
14069                 ExitAnalyzeMode();
14070                 ModeHighlight();
14071                 return 0;
14072         }
14073         appData.icsEngineAnalyze = TRUE;
14074         if (appData.debugMode)
14075             fprintf(debugFP, "ICS engine analyze starting... \n");
14076     }
14077
14078     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14079     if (appData.noChessProgram || gameMode == AnalyzeMode)
14080       return 0;
14081
14082     if (gameMode != AnalyzeFile) {
14083         if (!appData.icsEngineAnalyze) {
14084                EditGameEvent();
14085                if (gameMode != EditGame) return 0;
14086         }
14087         if (!appData.showThinking) ToggleShowThinking();
14088         ResurrectChessProgram();
14089         SendToProgram("analyze\n", &first);
14090         first.analyzing = TRUE;
14091         /*first.maybeThinking = TRUE;*/
14092         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14093         EngineOutputPopUp();
14094     }
14095     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14096     pausing = FALSE;
14097     ModeHighlight();
14098     SetGameInfo();
14099
14100     StartAnalysisClock();
14101     GetTimeMark(&lastNodeCountTime);
14102     lastNodeCount = 0;
14103     return 1;
14104 }
14105
14106 void
14107 AnalyzeFileEvent ()
14108 {
14109     if (appData.noChessProgram || gameMode == AnalyzeFile)
14110       return;
14111
14112     if (!first.analysisSupport) {
14113       char buf[MSG_SIZ];
14114       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14115       DisplayError(buf, 0);
14116       return;
14117     }
14118
14119     if (gameMode != AnalyzeMode) {
14120         keepInfo = 1; // mere annotating should not alter PGN tags
14121         EditGameEvent();
14122         keepInfo = 0;
14123         if (gameMode != EditGame) return;
14124         if (!appData.showThinking) ToggleShowThinking();
14125         ResurrectChessProgram();
14126         SendToProgram("analyze\n", &first);
14127         first.analyzing = TRUE;
14128         /*first.maybeThinking = TRUE;*/
14129         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14130         EngineOutputPopUp();
14131     }
14132     gameMode = AnalyzeFile;
14133     pausing = FALSE;
14134     ModeHighlight();
14135
14136     StartAnalysisClock();
14137     GetTimeMark(&lastNodeCountTime);
14138     lastNodeCount = 0;
14139     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14140     AnalysisPeriodicEvent(1);
14141 }
14142
14143 void
14144 MachineWhiteEvent ()
14145 {
14146     char buf[MSG_SIZ];
14147     char *bookHit = NULL;
14148
14149     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14150       return;
14151
14152
14153     if (gameMode == PlayFromGameFile ||
14154         gameMode == TwoMachinesPlay  ||
14155         gameMode == Training         ||
14156         gameMode == AnalyzeMode      ||
14157         gameMode == EndOfGame)
14158         EditGameEvent();
14159
14160     if (gameMode == EditPosition)
14161         EditPositionDone(TRUE);
14162
14163     if (!WhiteOnMove(currentMove)) {
14164         DisplayError(_("It is not White's turn"), 0);
14165         return;
14166     }
14167
14168     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14169       ExitAnalyzeMode();
14170
14171     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14172         gameMode == AnalyzeFile)
14173         TruncateGame();
14174
14175     ResurrectChessProgram();    /* in case it isn't running */
14176     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14177         gameMode = MachinePlaysWhite;
14178         ResetClocks();
14179     } else
14180     gameMode = MachinePlaysWhite;
14181     pausing = FALSE;
14182     ModeHighlight();
14183     SetGameInfo();
14184     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14185     DisplayTitle(buf);
14186     if (first.sendName) {
14187       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14188       SendToProgram(buf, &first);
14189     }
14190     if (first.sendTime) {
14191       if (first.useColors) {
14192         SendToProgram("black\n", &first); /*gnu kludge*/
14193       }
14194       SendTimeRemaining(&first, TRUE);
14195     }
14196     if (first.useColors) {
14197       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14198     }
14199     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14200     SetMachineThinkingEnables();
14201     first.maybeThinking = TRUE;
14202     StartClocks();
14203     firstMove = FALSE;
14204
14205     if (appData.autoFlipView && !flipView) {
14206       flipView = !flipView;
14207       DrawPosition(FALSE, NULL);
14208       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14209     }
14210
14211     if(bookHit) { // [HGM] book: simulate book reply
14212         static char bookMove[MSG_SIZ]; // a bit generous?
14213
14214         programStats.nodes = programStats.depth = programStats.time =
14215         programStats.score = programStats.got_only_move = 0;
14216         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14217
14218         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14219         strcat(bookMove, bookHit);
14220         HandleMachineMove(bookMove, &first);
14221     }
14222 }
14223
14224 void
14225 MachineBlackEvent ()
14226 {
14227   char buf[MSG_SIZ];
14228   char *bookHit = NULL;
14229
14230     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14231         return;
14232
14233
14234     if (gameMode == PlayFromGameFile ||
14235         gameMode == TwoMachinesPlay  ||
14236         gameMode == Training         ||
14237         gameMode == AnalyzeMode      ||
14238         gameMode == EndOfGame)
14239         EditGameEvent();
14240
14241     if (gameMode == EditPosition)
14242         EditPositionDone(TRUE);
14243
14244     if (WhiteOnMove(currentMove)) {
14245         DisplayError(_("It is not Black's turn"), 0);
14246         return;
14247     }
14248
14249     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14250       ExitAnalyzeMode();
14251
14252     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14253         gameMode == AnalyzeFile)
14254         TruncateGame();
14255
14256     ResurrectChessProgram();    /* in case it isn't running */
14257     gameMode = MachinePlaysBlack;
14258     pausing = FALSE;
14259     ModeHighlight();
14260     SetGameInfo();
14261     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14262     DisplayTitle(buf);
14263     if (first.sendName) {
14264       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14265       SendToProgram(buf, &first);
14266     }
14267     if (first.sendTime) {
14268       if (first.useColors) {
14269         SendToProgram("white\n", &first); /*gnu kludge*/
14270       }
14271       SendTimeRemaining(&first, FALSE);
14272     }
14273     if (first.useColors) {
14274       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14275     }
14276     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14277     SetMachineThinkingEnables();
14278     first.maybeThinking = TRUE;
14279     StartClocks();
14280
14281     if (appData.autoFlipView && flipView) {
14282       flipView = !flipView;
14283       DrawPosition(FALSE, NULL);
14284       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14285     }
14286     if(bookHit) { // [HGM] book: simulate book reply
14287         static char bookMove[MSG_SIZ]; // a bit generous?
14288
14289         programStats.nodes = programStats.depth = programStats.time =
14290         programStats.score = programStats.got_only_move = 0;
14291         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14292
14293         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14294         strcat(bookMove, bookHit);
14295         HandleMachineMove(bookMove, &first);
14296     }
14297 }
14298
14299
14300 void
14301 DisplayTwoMachinesTitle ()
14302 {
14303     char buf[MSG_SIZ];
14304     if (appData.matchGames > 0) {
14305         if(appData.tourneyFile[0]) {
14306           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14307                    gameInfo.white, _("vs."), gameInfo.black,
14308                    nextGame+1, appData.matchGames+1,
14309                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14310         } else
14311         if (first.twoMachinesColor[0] == 'w') {
14312           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14313                    gameInfo.white, _("vs."),  gameInfo.black,
14314                    first.matchWins, second.matchWins,
14315                    matchGame - 1 - (first.matchWins + second.matchWins));
14316         } else {
14317           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14318                    gameInfo.white, _("vs."), gameInfo.black,
14319                    second.matchWins, first.matchWins,
14320                    matchGame - 1 - (first.matchWins + second.matchWins));
14321         }
14322     } else {
14323       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14324     }
14325     DisplayTitle(buf);
14326 }
14327
14328 void
14329 SettingsMenuIfReady ()
14330 {
14331   if (second.lastPing != second.lastPong) {
14332     DisplayMessage("", _("Waiting for second chess program"));
14333     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14334     return;
14335   }
14336   ThawUI();
14337   DisplayMessage("", "");
14338   SettingsPopUp(&second);
14339 }
14340
14341 int
14342 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14343 {
14344     char buf[MSG_SIZ];
14345     if (cps->pr == NoProc) {
14346         StartChessProgram(cps);
14347         if (cps->protocolVersion == 1) {
14348           retry();
14349           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14350         } else {
14351           /* kludge: allow timeout for initial "feature" command */
14352           if(retry != TwoMachinesEventIfReady) FreezeUI();
14353           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14354           DisplayMessage("", buf);
14355           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14356         }
14357         return 1;
14358     }
14359     return 0;
14360 }
14361
14362 void
14363 TwoMachinesEvent P((void))
14364 {
14365     int i;
14366     char buf[MSG_SIZ];
14367     ChessProgramState *onmove;
14368     char *bookHit = NULL;
14369     static int stalling = 0;
14370     TimeMark now;
14371     long wait;
14372
14373     if (appData.noChessProgram) return;
14374
14375     switch (gameMode) {
14376       case TwoMachinesPlay:
14377         return;
14378       case MachinePlaysWhite:
14379       case MachinePlaysBlack:
14380         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14381             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14382             return;
14383         }
14384         /* fall through */
14385       case BeginningOfGame:
14386       case PlayFromGameFile:
14387       case EndOfGame:
14388         EditGameEvent();
14389         if (gameMode != EditGame) return;
14390         break;
14391       case EditPosition:
14392         EditPositionDone(TRUE);
14393         break;
14394       case AnalyzeMode:
14395       case AnalyzeFile:
14396         ExitAnalyzeMode();
14397         break;
14398       case EditGame:
14399       default:
14400         break;
14401     }
14402
14403 //    forwardMostMove = currentMove;
14404     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14405     startingEngine = TRUE;
14406
14407     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14408
14409     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14410     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14411       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14412       return;
14413     }
14414     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14415
14416     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14417                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14418         startingEngine = FALSE;
14419         DisplayError("second engine does not play this", 0);
14420         return;
14421     }
14422
14423     if(!stalling) {
14424       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14425       SendToProgram("force\n", &second);
14426       stalling = 1;
14427       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14428       return;
14429     }
14430     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14431     if(appData.matchPause>10000 || appData.matchPause<10)
14432                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14433     wait = SubtractTimeMarks(&now, &pauseStart);
14434     if(wait < appData.matchPause) {
14435         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14436         return;
14437     }
14438     // we are now committed to starting the game
14439     stalling = 0;
14440     DisplayMessage("", "");
14441     if (startedFromSetupPosition) {
14442         SendBoard(&second, backwardMostMove);
14443     if (appData.debugMode) {
14444         fprintf(debugFP, "Two Machines\n");
14445     }
14446     }
14447     for (i = backwardMostMove; i < forwardMostMove; i++) {
14448         SendMoveToProgram(i, &second);
14449     }
14450
14451     gameMode = TwoMachinesPlay;
14452     pausing = startingEngine = FALSE;
14453     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14454     SetGameInfo();
14455     DisplayTwoMachinesTitle();
14456     firstMove = TRUE;
14457     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14458         onmove = &first;
14459     } else {
14460         onmove = &second;
14461     }
14462     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14463     SendToProgram(first.computerString, &first);
14464     if (first.sendName) {
14465       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14466       SendToProgram(buf, &first);
14467     }
14468     SendToProgram(second.computerString, &second);
14469     if (second.sendName) {
14470       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14471       SendToProgram(buf, &second);
14472     }
14473
14474     ResetClocks();
14475     if (!first.sendTime || !second.sendTime) {
14476         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14477         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14478     }
14479     if (onmove->sendTime) {
14480       if (onmove->useColors) {
14481         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14482       }
14483       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14484     }
14485     if (onmove->useColors) {
14486       SendToProgram(onmove->twoMachinesColor, onmove);
14487     }
14488     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14489 //    SendToProgram("go\n", onmove);
14490     onmove->maybeThinking = TRUE;
14491     SetMachineThinkingEnables();
14492
14493     StartClocks();
14494
14495     if(bookHit) { // [HGM] book: simulate book reply
14496         static char bookMove[MSG_SIZ]; // a bit generous?
14497
14498         programStats.nodes = programStats.depth = programStats.time =
14499         programStats.score = programStats.got_only_move = 0;
14500         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14501
14502         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14503         strcat(bookMove, bookHit);
14504         savedMessage = bookMove; // args for deferred call
14505         savedState = onmove;
14506         ScheduleDelayedEvent(DeferredBookMove, 1);
14507     }
14508 }
14509
14510 void
14511 TrainingEvent ()
14512 {
14513     if (gameMode == Training) {
14514       SetTrainingModeOff();
14515       gameMode = PlayFromGameFile;
14516       DisplayMessage("", _("Training mode off"));
14517     } else {
14518       gameMode = Training;
14519       animateTraining = appData.animate;
14520
14521       /* make sure we are not already at the end of the game */
14522       if (currentMove < forwardMostMove) {
14523         SetTrainingModeOn();
14524         DisplayMessage("", _("Training mode on"));
14525       } else {
14526         gameMode = PlayFromGameFile;
14527         DisplayError(_("Already at end of game"), 0);
14528       }
14529     }
14530     ModeHighlight();
14531 }
14532
14533 void
14534 IcsClientEvent ()
14535 {
14536     if (!appData.icsActive) return;
14537     switch (gameMode) {
14538       case IcsPlayingWhite:
14539       case IcsPlayingBlack:
14540       case IcsObserving:
14541       case IcsIdle:
14542       case BeginningOfGame:
14543       case IcsExamining:
14544         return;
14545
14546       case EditGame:
14547         break;
14548
14549       case EditPosition:
14550         EditPositionDone(TRUE);
14551         break;
14552
14553       case AnalyzeMode:
14554       case AnalyzeFile:
14555         ExitAnalyzeMode();
14556         break;
14557
14558       default:
14559         EditGameEvent();
14560         break;
14561     }
14562
14563     gameMode = IcsIdle;
14564     ModeHighlight();
14565     return;
14566 }
14567
14568 void
14569 EditGameEvent ()
14570 {
14571     int i;
14572
14573     switch (gameMode) {
14574       case Training:
14575         SetTrainingModeOff();
14576         break;
14577       case MachinePlaysWhite:
14578       case MachinePlaysBlack:
14579       case BeginningOfGame:
14580         SendToProgram("force\n", &first);
14581         SetUserThinkingEnables();
14582         break;
14583       case PlayFromGameFile:
14584         (void) StopLoadGameTimer();
14585         if (gameFileFP != NULL) {
14586             gameFileFP = NULL;
14587         }
14588         break;
14589       case EditPosition:
14590         EditPositionDone(TRUE);
14591         break;
14592       case AnalyzeMode:
14593       case AnalyzeFile:
14594         ExitAnalyzeMode();
14595         SendToProgram("force\n", &first);
14596         break;
14597       case TwoMachinesPlay:
14598         GameEnds(EndOfFile, NULL, GE_PLAYER);
14599         ResurrectChessProgram();
14600         SetUserThinkingEnables();
14601         break;
14602       case EndOfGame:
14603         ResurrectChessProgram();
14604         break;
14605       case IcsPlayingBlack:
14606       case IcsPlayingWhite:
14607         DisplayError(_("Warning: You are still playing a game"), 0);
14608         break;
14609       case IcsObserving:
14610         DisplayError(_("Warning: You are still observing a game"), 0);
14611         break;
14612       case IcsExamining:
14613         DisplayError(_("Warning: You are still examining a game"), 0);
14614         break;
14615       case IcsIdle:
14616         break;
14617       case EditGame:
14618       default:
14619         return;
14620     }
14621
14622     pausing = FALSE;
14623     StopClocks();
14624     first.offeredDraw = second.offeredDraw = 0;
14625
14626     if (gameMode == PlayFromGameFile) {
14627         whiteTimeRemaining = timeRemaining[0][currentMove];
14628         blackTimeRemaining = timeRemaining[1][currentMove];
14629         DisplayTitle("");
14630     }
14631
14632     if (gameMode == MachinePlaysWhite ||
14633         gameMode == MachinePlaysBlack ||
14634         gameMode == TwoMachinesPlay ||
14635         gameMode == EndOfGame) {
14636         i = forwardMostMove;
14637         while (i > currentMove) {
14638             SendToProgram("undo\n", &first);
14639             i--;
14640         }
14641         if(!adjustedClock) {
14642         whiteTimeRemaining = timeRemaining[0][currentMove];
14643         blackTimeRemaining = timeRemaining[1][currentMove];
14644         DisplayBothClocks();
14645         }
14646         if (whiteFlag || blackFlag) {
14647             whiteFlag = blackFlag = 0;
14648         }
14649         DisplayTitle("");
14650     }
14651
14652     gameMode = EditGame;
14653     ModeHighlight();
14654     SetGameInfo();
14655 }
14656
14657
14658 void
14659 EditPositionEvent ()
14660 {
14661     if (gameMode == EditPosition) {
14662         EditGameEvent();
14663         return;
14664     }
14665
14666     EditGameEvent();
14667     if (gameMode != EditGame) return;
14668
14669     gameMode = EditPosition;
14670     ModeHighlight();
14671     SetGameInfo();
14672     if (currentMove > 0)
14673       CopyBoard(boards[0], boards[currentMove]);
14674
14675     blackPlaysFirst = !WhiteOnMove(currentMove);
14676     ResetClocks();
14677     currentMove = forwardMostMove = backwardMostMove = 0;
14678     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14679     DisplayMove(-1);
14680     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14681 }
14682
14683 void
14684 ExitAnalyzeMode ()
14685 {
14686     /* [DM] icsEngineAnalyze - possible call from other functions */
14687     if (appData.icsEngineAnalyze) {
14688         appData.icsEngineAnalyze = FALSE;
14689
14690         DisplayMessage("",_("Close ICS engine analyze..."));
14691     }
14692     if (first.analysisSupport && first.analyzing) {
14693       SendToBoth("exit\n");
14694       first.analyzing = second.analyzing = FALSE;
14695     }
14696     thinkOutput[0] = NULLCHAR;
14697 }
14698
14699 void
14700 EditPositionDone (Boolean fakeRights)
14701 {
14702     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14703
14704     startedFromSetupPosition = TRUE;
14705     InitChessProgram(&first, FALSE);
14706     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14707       boards[0][EP_STATUS] = EP_NONE;
14708       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14709       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14710         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14711         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14712       } else boards[0][CASTLING][2] = NoRights;
14713       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14714         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14715         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14716       } else boards[0][CASTLING][5] = NoRights;
14717       if(gameInfo.variant == VariantSChess) {
14718         int i;
14719         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14720           boards[0][VIRGIN][i] = 0;
14721           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14722           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14723         }
14724       }
14725     }
14726     SendToProgram("force\n", &first);
14727     if (blackPlaysFirst) {
14728         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14729         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14730         currentMove = forwardMostMove = backwardMostMove = 1;
14731         CopyBoard(boards[1], boards[0]);
14732     } else {
14733         currentMove = forwardMostMove = backwardMostMove = 0;
14734     }
14735     SendBoard(&first, forwardMostMove);
14736     if (appData.debugMode) {
14737         fprintf(debugFP, "EditPosDone\n");
14738     }
14739     DisplayTitle("");
14740     DisplayMessage("", "");
14741     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14742     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14743     gameMode = EditGame;
14744     ModeHighlight();
14745     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14746     ClearHighlights(); /* [AS] */
14747 }
14748
14749 /* Pause for `ms' milliseconds */
14750 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14751 void
14752 TimeDelay (long ms)
14753 {
14754     TimeMark m1, m2;
14755
14756     GetTimeMark(&m1);
14757     do {
14758         GetTimeMark(&m2);
14759     } while (SubtractTimeMarks(&m2, &m1) < ms);
14760 }
14761
14762 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14763 void
14764 SendMultiLineToICS (char *buf)
14765 {
14766     char temp[MSG_SIZ+1], *p;
14767     int len;
14768
14769     len = strlen(buf);
14770     if (len > MSG_SIZ)
14771       len = MSG_SIZ;
14772
14773     strncpy(temp, buf, len);
14774     temp[len] = 0;
14775
14776     p = temp;
14777     while (*p) {
14778         if (*p == '\n' || *p == '\r')
14779           *p = ' ';
14780         ++p;
14781     }
14782
14783     strcat(temp, "\n");
14784     SendToICS(temp);
14785     SendToPlayer(temp, strlen(temp));
14786 }
14787
14788 void
14789 SetWhiteToPlayEvent ()
14790 {
14791     if (gameMode == EditPosition) {
14792         blackPlaysFirst = FALSE;
14793         DisplayBothClocks();    /* works because currentMove is 0 */
14794     } else if (gameMode == IcsExamining) {
14795         SendToICS(ics_prefix);
14796         SendToICS("tomove white\n");
14797     }
14798 }
14799
14800 void
14801 SetBlackToPlayEvent ()
14802 {
14803     if (gameMode == EditPosition) {
14804         blackPlaysFirst = TRUE;
14805         currentMove = 1;        /* kludge */
14806         DisplayBothClocks();
14807         currentMove = 0;
14808     } else if (gameMode == IcsExamining) {
14809         SendToICS(ics_prefix);
14810         SendToICS("tomove black\n");
14811     }
14812 }
14813
14814 void
14815 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14816 {
14817     char buf[MSG_SIZ];
14818     ChessSquare piece = boards[0][y][x];
14819     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14820     static int lastVariant;
14821
14822     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14823
14824     switch (selection) {
14825       case ClearBoard:
14826         CopyBoard(currentBoard, boards[0]);
14827         CopyBoard(menuBoard, initialPosition);
14828         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14829             SendToICS(ics_prefix);
14830             SendToICS("bsetup clear\n");
14831         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14832             SendToICS(ics_prefix);
14833             SendToICS("clearboard\n");
14834         } else {
14835             int nonEmpty = 0;
14836             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14837                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14838                 for (y = 0; y < BOARD_HEIGHT; y++) {
14839                     if (gameMode == IcsExamining) {
14840                         if (boards[currentMove][y][x] != EmptySquare) {
14841                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14842                                     AAA + x, ONE + y);
14843                             SendToICS(buf);
14844                         }
14845                     } else {
14846                         if(boards[0][y][x] != p) nonEmpty++;
14847                         boards[0][y][x] = p;
14848                     }
14849                 }
14850                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14851             }
14852             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14853                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14854                     ChessSquare p = menuBoard[0][x];
14855                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14856                     p = menuBoard[BOARD_HEIGHT-1][x];
14857                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14858                 }
14859                 DisplayMessage("Clicking clock again restores position", "");
14860                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14861                 if(!nonEmpty) { // asked to clear an empty board
14862                     CopyBoard(boards[0], menuBoard);
14863                 } else
14864                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14865                     CopyBoard(boards[0], initialPosition);
14866                 } else
14867                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14868                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14869                     CopyBoard(boards[0], erasedBoard);
14870                 } else
14871                     CopyBoard(erasedBoard, currentBoard);
14872
14873             }
14874         }
14875         if (gameMode == EditPosition) {
14876             DrawPosition(FALSE, boards[0]);
14877         }
14878         break;
14879
14880       case WhitePlay:
14881         SetWhiteToPlayEvent();
14882         break;
14883
14884       case BlackPlay:
14885         SetBlackToPlayEvent();
14886         break;
14887
14888       case EmptySquare:
14889         if (gameMode == IcsExamining) {
14890             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14891             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14892             SendToICS(buf);
14893         } else {
14894             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14895                 if(x == BOARD_LEFT-2) {
14896                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14897                     boards[0][y][1] = 0;
14898                 } else
14899                 if(x == BOARD_RGHT+1) {
14900                     if(y >= gameInfo.holdingsSize) break;
14901                     boards[0][y][BOARD_WIDTH-2] = 0;
14902                 } else break;
14903             }
14904             boards[0][y][x] = EmptySquare;
14905             DrawPosition(FALSE, boards[0]);
14906         }
14907         break;
14908
14909       case PromotePiece:
14910         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14911            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14912             selection = (ChessSquare) (PROMOTED piece);
14913         } else if(piece == EmptySquare) selection = WhiteSilver;
14914         else selection = (ChessSquare)((int)piece - 1);
14915         goto defaultlabel;
14916
14917       case DemotePiece:
14918         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14919            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14920             selection = (ChessSquare) (DEMOTED piece);
14921         } else if(piece == EmptySquare) selection = BlackSilver;
14922         else selection = (ChessSquare)((int)piece + 1);
14923         goto defaultlabel;
14924
14925       case WhiteQueen:
14926       case BlackQueen:
14927         if(gameInfo.variant == VariantShatranj ||
14928            gameInfo.variant == VariantXiangqi  ||
14929            gameInfo.variant == VariantCourier  ||
14930            gameInfo.variant == VariantASEAN    ||
14931            gameInfo.variant == VariantMakruk     )
14932             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14933         goto defaultlabel;
14934
14935       case WhiteKing:
14936       case BlackKing:
14937         if(gameInfo.variant == VariantXiangqi)
14938             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14939         if(gameInfo.variant == VariantKnightmate)
14940             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14941       default:
14942         defaultlabel:
14943         if (gameMode == IcsExamining) {
14944             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14945             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14946                      PieceToChar(selection), AAA + x, ONE + y);
14947             SendToICS(buf);
14948         } else {
14949             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14950                 int n;
14951                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14952                     n = PieceToNumber(selection - BlackPawn);
14953                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14954                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14955                     boards[0][BOARD_HEIGHT-1-n][1]++;
14956                 } else
14957                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14958                     n = PieceToNumber(selection);
14959                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14960                     boards[0][n][BOARD_WIDTH-1] = selection;
14961                     boards[0][n][BOARD_WIDTH-2]++;
14962                 }
14963             } else
14964             boards[0][y][x] = selection;
14965             DrawPosition(TRUE, boards[0]);
14966             ClearHighlights();
14967             fromX = fromY = -1;
14968         }
14969         break;
14970     }
14971 }
14972
14973
14974 void
14975 DropMenuEvent (ChessSquare selection, int x, int y)
14976 {
14977     ChessMove moveType;
14978
14979     switch (gameMode) {
14980       case IcsPlayingWhite:
14981       case MachinePlaysBlack:
14982         if (!WhiteOnMove(currentMove)) {
14983             DisplayMoveError(_("It is Black's turn"));
14984             return;
14985         }
14986         moveType = WhiteDrop;
14987         break;
14988       case IcsPlayingBlack:
14989       case MachinePlaysWhite:
14990         if (WhiteOnMove(currentMove)) {
14991             DisplayMoveError(_("It is White's turn"));
14992             return;
14993         }
14994         moveType = BlackDrop;
14995         break;
14996       case EditGame:
14997         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14998         break;
14999       default:
15000         return;
15001     }
15002
15003     if (moveType == BlackDrop && selection < BlackPawn) {
15004       selection = (ChessSquare) ((int) selection
15005                                  + (int) BlackPawn - (int) WhitePawn);
15006     }
15007     if (boards[currentMove][y][x] != EmptySquare) {
15008         DisplayMoveError(_("That square is occupied"));
15009         return;
15010     }
15011
15012     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15013 }
15014
15015 void
15016 AcceptEvent ()
15017 {
15018     /* Accept a pending offer of any kind from opponent */
15019
15020     if (appData.icsActive) {
15021         SendToICS(ics_prefix);
15022         SendToICS("accept\n");
15023     } else if (cmailMsgLoaded) {
15024         if (currentMove == cmailOldMove &&
15025             commentList[cmailOldMove] != NULL &&
15026             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15027                    "Black offers a draw" : "White offers a draw")) {
15028             TruncateGame();
15029             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15030             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15031         } else {
15032             DisplayError(_("There is no pending offer on this move"), 0);
15033             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15034         }
15035     } else {
15036         /* Not used for offers from chess program */
15037     }
15038 }
15039
15040 void
15041 DeclineEvent ()
15042 {
15043     /* Decline a pending offer of any kind from opponent */
15044
15045     if (appData.icsActive) {
15046         SendToICS(ics_prefix);
15047         SendToICS("decline\n");
15048     } else if (cmailMsgLoaded) {
15049         if (currentMove == cmailOldMove &&
15050             commentList[cmailOldMove] != NULL &&
15051             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15052                    "Black offers a draw" : "White offers a draw")) {
15053 #ifdef NOTDEF
15054             AppendComment(cmailOldMove, "Draw declined", TRUE);
15055             DisplayComment(cmailOldMove - 1, "Draw declined");
15056 #endif /*NOTDEF*/
15057         } else {
15058             DisplayError(_("There is no pending offer on this move"), 0);
15059         }
15060     } else {
15061         /* Not used for offers from chess program */
15062     }
15063 }
15064
15065 void
15066 RematchEvent ()
15067 {
15068     /* Issue ICS rematch command */
15069     if (appData.icsActive) {
15070         SendToICS(ics_prefix);
15071         SendToICS("rematch\n");
15072     }
15073 }
15074
15075 void
15076 CallFlagEvent ()
15077 {
15078     /* Call your opponent's flag (claim a win on time) */
15079     if (appData.icsActive) {
15080         SendToICS(ics_prefix);
15081         SendToICS("flag\n");
15082     } else {
15083         switch (gameMode) {
15084           default:
15085             return;
15086           case MachinePlaysWhite:
15087             if (whiteFlag) {
15088                 if (blackFlag)
15089                   GameEnds(GameIsDrawn, "Both players ran out of time",
15090                            GE_PLAYER);
15091                 else
15092                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15093             } else {
15094                 DisplayError(_("Your opponent is not out of time"), 0);
15095             }
15096             break;
15097           case MachinePlaysBlack:
15098             if (blackFlag) {
15099                 if (whiteFlag)
15100                   GameEnds(GameIsDrawn, "Both players ran out of time",
15101                            GE_PLAYER);
15102                 else
15103                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15104             } else {
15105                 DisplayError(_("Your opponent is not out of time"), 0);
15106             }
15107             break;
15108         }
15109     }
15110 }
15111
15112 void
15113 ClockClick (int which)
15114 {       // [HGM] code moved to back-end from winboard.c
15115         if(which) { // black clock
15116           if (gameMode == EditPosition || gameMode == IcsExamining) {
15117             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15118             SetBlackToPlayEvent();
15119           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15120           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15121           } else if (shiftKey) {
15122             AdjustClock(which, -1);
15123           } else if (gameMode == IcsPlayingWhite ||
15124                      gameMode == MachinePlaysBlack) {
15125             CallFlagEvent();
15126           }
15127         } else { // white clock
15128           if (gameMode == EditPosition || gameMode == IcsExamining) {
15129             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15130             SetWhiteToPlayEvent();
15131           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15132           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15133           } else if (shiftKey) {
15134             AdjustClock(which, -1);
15135           } else if (gameMode == IcsPlayingBlack ||
15136                    gameMode == MachinePlaysWhite) {
15137             CallFlagEvent();
15138           }
15139         }
15140 }
15141
15142 void
15143 DrawEvent ()
15144 {
15145     /* Offer draw or accept pending draw offer from opponent */
15146
15147     if (appData.icsActive) {
15148         /* Note: tournament rules require draw offers to be
15149            made after you make your move but before you punch
15150            your clock.  Currently ICS doesn't let you do that;
15151            instead, you immediately punch your clock after making
15152            a move, but you can offer a draw at any time. */
15153
15154         SendToICS(ics_prefix);
15155         SendToICS("draw\n");
15156         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15157     } else if (cmailMsgLoaded) {
15158         if (currentMove == cmailOldMove &&
15159             commentList[cmailOldMove] != NULL &&
15160             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15161                    "Black offers a draw" : "White offers a draw")) {
15162             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15163             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15164         } else if (currentMove == cmailOldMove + 1) {
15165             char *offer = WhiteOnMove(cmailOldMove) ?
15166               "White offers a draw" : "Black offers a draw";
15167             AppendComment(currentMove, offer, TRUE);
15168             DisplayComment(currentMove - 1, offer);
15169             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15170         } else {
15171             DisplayError(_("You must make your move before offering a draw"), 0);
15172             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15173         }
15174     } else if (first.offeredDraw) {
15175         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15176     } else {
15177         if (first.sendDrawOffers) {
15178             SendToProgram("draw\n", &first);
15179             userOfferedDraw = TRUE;
15180         }
15181     }
15182 }
15183
15184 void
15185 AdjournEvent ()
15186 {
15187     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15188
15189     if (appData.icsActive) {
15190         SendToICS(ics_prefix);
15191         SendToICS("adjourn\n");
15192     } else {
15193         /* Currently GNU Chess doesn't offer or accept Adjourns */
15194     }
15195 }
15196
15197
15198 void
15199 AbortEvent ()
15200 {
15201     /* Offer Abort or accept pending Abort offer from opponent */
15202
15203     if (appData.icsActive) {
15204         SendToICS(ics_prefix);
15205         SendToICS("abort\n");
15206     } else {
15207         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15208     }
15209 }
15210
15211 void
15212 ResignEvent ()
15213 {
15214     /* Resign.  You can do this even if it's not your turn. */
15215
15216     if (appData.icsActive) {
15217         SendToICS(ics_prefix);
15218         SendToICS("resign\n");
15219     } else {
15220         switch (gameMode) {
15221           case MachinePlaysWhite:
15222             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15223             break;
15224           case MachinePlaysBlack:
15225             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15226             break;
15227           case EditGame:
15228             if (cmailMsgLoaded) {
15229                 TruncateGame();
15230                 if (WhiteOnMove(cmailOldMove)) {
15231                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15232                 } else {
15233                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15234                 }
15235                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15236             }
15237             break;
15238           default:
15239             break;
15240         }
15241     }
15242 }
15243
15244
15245 void
15246 StopObservingEvent ()
15247 {
15248     /* Stop observing current games */
15249     SendToICS(ics_prefix);
15250     SendToICS("unobserve\n");
15251 }
15252
15253 void
15254 StopExaminingEvent ()
15255 {
15256     /* Stop observing current game */
15257     SendToICS(ics_prefix);
15258     SendToICS("unexamine\n");
15259 }
15260
15261 void
15262 ForwardInner (int target)
15263 {
15264     int limit; int oldSeekGraphUp = seekGraphUp;
15265
15266     if (appData.debugMode)
15267         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15268                 target, currentMove, forwardMostMove);
15269
15270     if (gameMode == EditPosition)
15271       return;
15272
15273     seekGraphUp = FALSE;
15274     MarkTargetSquares(1);
15275
15276     if (gameMode == PlayFromGameFile && !pausing)
15277       PauseEvent();
15278
15279     if (gameMode == IcsExamining && pausing)
15280       limit = pauseExamForwardMostMove;
15281     else
15282       limit = forwardMostMove;
15283
15284     if (target > limit) target = limit;
15285
15286     if (target > 0 && moveList[target - 1][0]) {
15287         int fromX, fromY, toX, toY;
15288         toX = moveList[target - 1][2] - AAA;
15289         toY = moveList[target - 1][3] - ONE;
15290         if (moveList[target - 1][1] == '@') {
15291             if (appData.highlightLastMove) {
15292                 SetHighlights(-1, -1, toX, toY);
15293             }
15294         } else {
15295             fromX = moveList[target - 1][0] - AAA;
15296             fromY = moveList[target - 1][1] - ONE;
15297             if (target == currentMove + 1) {
15298                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15299             }
15300             if (appData.highlightLastMove) {
15301                 SetHighlights(fromX, fromY, toX, toY);
15302             }
15303         }
15304     }
15305     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15306         gameMode == Training || gameMode == PlayFromGameFile ||
15307         gameMode == AnalyzeFile) {
15308         while (currentMove < target) {
15309             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15310             SendMoveToProgram(currentMove++, &first);
15311         }
15312     } else {
15313         currentMove = target;
15314     }
15315
15316     if (gameMode == EditGame || gameMode == EndOfGame) {
15317         whiteTimeRemaining = timeRemaining[0][currentMove];
15318         blackTimeRemaining = timeRemaining[1][currentMove];
15319     }
15320     DisplayBothClocks();
15321     DisplayMove(currentMove - 1);
15322     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15323     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15324     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15325         DisplayComment(currentMove - 1, commentList[currentMove]);
15326     }
15327     ClearMap(); // [HGM] exclude: invalidate map
15328 }
15329
15330
15331 void
15332 ForwardEvent ()
15333 {
15334     if (gameMode == IcsExamining && !pausing) {
15335         SendToICS(ics_prefix);
15336         SendToICS("forward\n");
15337     } else {
15338         ForwardInner(currentMove + 1);
15339     }
15340 }
15341
15342 void
15343 ToEndEvent ()
15344 {
15345     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15346         /* to optimze, we temporarily turn off analysis mode while we feed
15347          * the remaining moves to the engine. Otherwise we get analysis output
15348          * after each move.
15349          */
15350         if (first.analysisSupport) {
15351           SendToProgram("exit\nforce\n", &first);
15352           first.analyzing = FALSE;
15353         }
15354     }
15355
15356     if (gameMode == IcsExamining && !pausing) {
15357         SendToICS(ics_prefix);
15358         SendToICS("forward 999999\n");
15359     } else {
15360         ForwardInner(forwardMostMove);
15361     }
15362
15363     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15364         /* we have fed all the moves, so reactivate analysis mode */
15365         SendToProgram("analyze\n", &first);
15366         first.analyzing = TRUE;
15367         /*first.maybeThinking = TRUE;*/
15368         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15369     }
15370 }
15371
15372 void
15373 BackwardInner (int target)
15374 {
15375     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15376
15377     if (appData.debugMode)
15378         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15379                 target, currentMove, forwardMostMove);
15380
15381     if (gameMode == EditPosition) return;
15382     seekGraphUp = FALSE;
15383     MarkTargetSquares(1);
15384     if (currentMove <= backwardMostMove) {
15385         ClearHighlights();
15386         DrawPosition(full_redraw, boards[currentMove]);
15387         return;
15388     }
15389     if (gameMode == PlayFromGameFile && !pausing)
15390       PauseEvent();
15391
15392     if (moveList[target][0]) {
15393         int fromX, fromY, toX, toY;
15394         toX = moveList[target][2] - AAA;
15395         toY = moveList[target][3] - ONE;
15396         if (moveList[target][1] == '@') {
15397             if (appData.highlightLastMove) {
15398                 SetHighlights(-1, -1, toX, toY);
15399             }
15400         } else {
15401             fromX = moveList[target][0] - AAA;
15402             fromY = moveList[target][1] - ONE;
15403             if (target == currentMove - 1) {
15404                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15405             }
15406             if (appData.highlightLastMove) {
15407                 SetHighlights(fromX, fromY, toX, toY);
15408             }
15409         }
15410     }
15411     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15412         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15413         while (currentMove > target) {
15414             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15415                 // null move cannot be undone. Reload program with move history before it.
15416                 int i;
15417                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15418                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15419                 }
15420                 SendBoard(&first, i);
15421               if(second.analyzing) SendBoard(&second, i);
15422                 for(currentMove=i; currentMove<target; currentMove++) {
15423                     SendMoveToProgram(currentMove, &first);
15424                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15425                 }
15426                 break;
15427             }
15428             SendToBoth("undo\n");
15429             currentMove--;
15430         }
15431     } else {
15432         currentMove = target;
15433     }
15434
15435     if (gameMode == EditGame || gameMode == EndOfGame) {
15436         whiteTimeRemaining = timeRemaining[0][currentMove];
15437         blackTimeRemaining = timeRemaining[1][currentMove];
15438     }
15439     DisplayBothClocks();
15440     DisplayMove(currentMove - 1);
15441     DrawPosition(full_redraw, boards[currentMove]);
15442     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15443     // [HGM] PV info: routine tests if comment empty
15444     DisplayComment(currentMove - 1, commentList[currentMove]);
15445     ClearMap(); // [HGM] exclude: invalidate map
15446 }
15447
15448 void
15449 BackwardEvent ()
15450 {
15451     if (gameMode == IcsExamining && !pausing) {
15452         SendToICS(ics_prefix);
15453         SendToICS("backward\n");
15454     } else {
15455         BackwardInner(currentMove - 1);
15456     }
15457 }
15458
15459 void
15460 ToStartEvent ()
15461 {
15462     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15463         /* to optimize, we temporarily turn off analysis mode while we undo
15464          * all the moves. Otherwise we get analysis output after each undo.
15465          */
15466         if (first.analysisSupport) {
15467           SendToProgram("exit\nforce\n", &first);
15468           first.analyzing = FALSE;
15469         }
15470     }
15471
15472     if (gameMode == IcsExamining && !pausing) {
15473         SendToICS(ics_prefix);
15474         SendToICS("backward 999999\n");
15475     } else {
15476         BackwardInner(backwardMostMove);
15477     }
15478
15479     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15480         /* we have fed all the moves, so reactivate analysis mode */
15481         SendToProgram("analyze\n", &first);
15482         first.analyzing = TRUE;
15483         /*first.maybeThinking = TRUE;*/
15484         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15485     }
15486 }
15487
15488 void
15489 ToNrEvent (int to)
15490 {
15491   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15492   if (to >= forwardMostMove) to = forwardMostMove;
15493   if (to <= backwardMostMove) to = backwardMostMove;
15494   if (to < currentMove) {
15495     BackwardInner(to);
15496   } else {
15497     ForwardInner(to);
15498   }
15499 }
15500
15501 void
15502 RevertEvent (Boolean annotate)
15503 {
15504     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15505         return;
15506     }
15507     if (gameMode != IcsExamining) {
15508         DisplayError(_("You are not examining a game"), 0);
15509         return;
15510     }
15511     if (pausing) {
15512         DisplayError(_("You can't revert while pausing"), 0);
15513         return;
15514     }
15515     SendToICS(ics_prefix);
15516     SendToICS("revert\n");
15517 }
15518
15519 void
15520 RetractMoveEvent ()
15521 {
15522     switch (gameMode) {
15523       case MachinePlaysWhite:
15524       case MachinePlaysBlack:
15525         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15526             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15527             return;
15528         }
15529         if (forwardMostMove < 2) return;
15530         currentMove = forwardMostMove = forwardMostMove - 2;
15531         whiteTimeRemaining = timeRemaining[0][currentMove];
15532         blackTimeRemaining = timeRemaining[1][currentMove];
15533         DisplayBothClocks();
15534         DisplayMove(currentMove - 1);
15535         ClearHighlights();/*!! could figure this out*/
15536         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15537         SendToProgram("remove\n", &first);
15538         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15539         break;
15540
15541       case BeginningOfGame:
15542       default:
15543         break;
15544
15545       case IcsPlayingWhite:
15546       case IcsPlayingBlack:
15547         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15548             SendToICS(ics_prefix);
15549             SendToICS("takeback 2\n");
15550         } else {
15551             SendToICS(ics_prefix);
15552             SendToICS("takeback 1\n");
15553         }
15554         break;
15555     }
15556 }
15557
15558 void
15559 MoveNowEvent ()
15560 {
15561     ChessProgramState *cps;
15562
15563     switch (gameMode) {
15564       case MachinePlaysWhite:
15565         if (!WhiteOnMove(forwardMostMove)) {
15566             DisplayError(_("It is your turn"), 0);
15567             return;
15568         }
15569         cps = &first;
15570         break;
15571       case MachinePlaysBlack:
15572         if (WhiteOnMove(forwardMostMove)) {
15573             DisplayError(_("It is your turn"), 0);
15574             return;
15575         }
15576         cps = &first;
15577         break;
15578       case TwoMachinesPlay:
15579         if (WhiteOnMove(forwardMostMove) ==
15580             (first.twoMachinesColor[0] == 'w')) {
15581             cps = &first;
15582         } else {
15583             cps = &second;
15584         }
15585         break;
15586       case BeginningOfGame:
15587       default:
15588         return;
15589     }
15590     SendToProgram("?\n", cps);
15591 }
15592
15593 void
15594 TruncateGameEvent ()
15595 {
15596     EditGameEvent();
15597     if (gameMode != EditGame) return;
15598     TruncateGame();
15599 }
15600
15601 void
15602 TruncateGame ()
15603 {
15604     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15605     if (forwardMostMove > currentMove) {
15606         if (gameInfo.resultDetails != NULL) {
15607             free(gameInfo.resultDetails);
15608             gameInfo.resultDetails = NULL;
15609             gameInfo.result = GameUnfinished;
15610         }
15611         forwardMostMove = currentMove;
15612         HistorySet(parseList, backwardMostMove, forwardMostMove,
15613                    currentMove-1);
15614     }
15615 }
15616
15617 void
15618 HintEvent ()
15619 {
15620     if (appData.noChessProgram) return;
15621     switch (gameMode) {
15622       case MachinePlaysWhite:
15623         if (WhiteOnMove(forwardMostMove)) {
15624             DisplayError(_("Wait until your turn."), 0);
15625             return;
15626         }
15627         break;
15628       case BeginningOfGame:
15629       case MachinePlaysBlack:
15630         if (!WhiteOnMove(forwardMostMove)) {
15631             DisplayError(_("Wait until your turn."), 0);
15632             return;
15633         }
15634         break;
15635       default:
15636         DisplayError(_("No hint available"), 0);
15637         return;
15638     }
15639     SendToProgram("hint\n", &first);
15640     hintRequested = TRUE;
15641 }
15642
15643 void
15644 CreateBookEvent ()
15645 {
15646     ListGame * lg = (ListGame *) gameList.head;
15647     FILE *f, *g;
15648     int nItem;
15649     static int secondTime = FALSE;
15650
15651     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15652         DisplayError(_("Game list not loaded or empty"), 0);
15653         return;
15654     }
15655
15656     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15657         fclose(g);
15658         secondTime++;
15659         DisplayNote(_("Book file exists! Try again for overwrite."));
15660         return;
15661     }
15662
15663     creatingBook = TRUE;
15664     secondTime = FALSE;
15665
15666     /* Get list size */
15667     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15668         LoadGame(f, nItem, "", TRUE);
15669         AddGameToBook(TRUE);
15670         lg = (ListGame *) lg->node.succ;
15671     }
15672
15673     creatingBook = FALSE;
15674     FlushBook();
15675 }
15676
15677 void
15678 BookEvent ()
15679 {
15680     if (appData.noChessProgram) return;
15681     switch (gameMode) {
15682       case MachinePlaysWhite:
15683         if (WhiteOnMove(forwardMostMove)) {
15684             DisplayError(_("Wait until your turn."), 0);
15685             return;
15686         }
15687         break;
15688       case BeginningOfGame:
15689       case MachinePlaysBlack:
15690         if (!WhiteOnMove(forwardMostMove)) {
15691             DisplayError(_("Wait until your turn."), 0);
15692             return;
15693         }
15694         break;
15695       case EditPosition:
15696         EditPositionDone(TRUE);
15697         break;
15698       case TwoMachinesPlay:
15699         return;
15700       default:
15701         break;
15702     }
15703     SendToProgram("bk\n", &first);
15704     bookOutput[0] = NULLCHAR;
15705     bookRequested = TRUE;
15706 }
15707
15708 void
15709 AboutGameEvent ()
15710 {
15711     char *tags = PGNTags(&gameInfo);
15712     TagsPopUp(tags, CmailMsg());
15713     free(tags);
15714 }
15715
15716 /* end button procedures */
15717
15718 void
15719 PrintPosition (FILE *fp, int move)
15720 {
15721     int i, j;
15722
15723     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15724         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15725             char c = PieceToChar(boards[move][i][j]);
15726             fputc(c == 'x' ? '.' : c, fp);
15727             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15728         }
15729     }
15730     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15731       fprintf(fp, "white to play\n");
15732     else
15733       fprintf(fp, "black to play\n");
15734 }
15735
15736 void
15737 PrintOpponents (FILE *fp)
15738 {
15739     if (gameInfo.white != NULL) {
15740         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15741     } else {
15742         fprintf(fp, "\n");
15743     }
15744 }
15745
15746 /* Find last component of program's own name, using some heuristics */
15747 void
15748 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15749 {
15750     char *p, *q, c;
15751     int local = (strcmp(host, "localhost") == 0);
15752     while (!local && (p = strchr(prog, ';')) != NULL) {
15753         p++;
15754         while (*p == ' ') p++;
15755         prog = p;
15756     }
15757     if (*prog == '"' || *prog == '\'') {
15758         q = strchr(prog + 1, *prog);
15759     } else {
15760         q = strchr(prog, ' ');
15761     }
15762     if (q == NULL) q = prog + strlen(prog);
15763     p = q;
15764     while (p >= prog && *p != '/' && *p != '\\') p--;
15765     p++;
15766     if(p == prog && *p == '"') p++;
15767     c = *q; *q = 0;
15768     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15769     memcpy(buf, p, q - p);
15770     buf[q - p] = NULLCHAR;
15771     if (!local) {
15772         strcat(buf, "@");
15773         strcat(buf, host);
15774     }
15775 }
15776
15777 char *
15778 TimeControlTagValue ()
15779 {
15780     char buf[MSG_SIZ];
15781     if (!appData.clockMode) {
15782       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15783     } else if (movesPerSession > 0) {
15784       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15785     } else if (timeIncrement == 0) {
15786       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15787     } else {
15788       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15789     }
15790     return StrSave(buf);
15791 }
15792
15793 void
15794 SetGameInfo ()
15795 {
15796     /* This routine is used only for certain modes */
15797     VariantClass v = gameInfo.variant;
15798     ChessMove r = GameUnfinished;
15799     char *p = NULL;
15800
15801     if(keepInfo) return;
15802
15803     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15804         r = gameInfo.result;
15805         p = gameInfo.resultDetails;
15806         gameInfo.resultDetails = NULL;
15807     }
15808     ClearGameInfo(&gameInfo);
15809     gameInfo.variant = v;
15810
15811     switch (gameMode) {
15812       case MachinePlaysWhite:
15813         gameInfo.event = StrSave( appData.pgnEventHeader );
15814         gameInfo.site = StrSave(HostName());
15815         gameInfo.date = PGNDate();
15816         gameInfo.round = StrSave("-");
15817         gameInfo.white = StrSave(first.tidy);
15818         gameInfo.black = StrSave(UserName());
15819         gameInfo.timeControl = TimeControlTagValue();
15820         break;
15821
15822       case MachinePlaysBlack:
15823         gameInfo.event = StrSave( appData.pgnEventHeader );
15824         gameInfo.site = StrSave(HostName());
15825         gameInfo.date = PGNDate();
15826         gameInfo.round = StrSave("-");
15827         gameInfo.white = StrSave(UserName());
15828         gameInfo.black = StrSave(first.tidy);
15829         gameInfo.timeControl = TimeControlTagValue();
15830         break;
15831
15832       case TwoMachinesPlay:
15833         gameInfo.event = StrSave( appData.pgnEventHeader );
15834         gameInfo.site = StrSave(HostName());
15835         gameInfo.date = PGNDate();
15836         if (roundNr > 0) {
15837             char buf[MSG_SIZ];
15838             snprintf(buf, MSG_SIZ, "%d", roundNr);
15839             gameInfo.round = StrSave(buf);
15840         } else {
15841             gameInfo.round = StrSave("-");
15842         }
15843         if (first.twoMachinesColor[0] == 'w') {
15844             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15845             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15846         } else {
15847             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15848             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15849         }
15850         gameInfo.timeControl = TimeControlTagValue();
15851         break;
15852
15853       case EditGame:
15854         gameInfo.event = StrSave("Edited game");
15855         gameInfo.site = StrSave(HostName());
15856         gameInfo.date = PGNDate();
15857         gameInfo.round = StrSave("-");
15858         gameInfo.white = StrSave("-");
15859         gameInfo.black = StrSave("-");
15860         gameInfo.result = r;
15861         gameInfo.resultDetails = p;
15862         break;
15863
15864       case EditPosition:
15865         gameInfo.event = StrSave("Edited position");
15866         gameInfo.site = StrSave(HostName());
15867         gameInfo.date = PGNDate();
15868         gameInfo.round = StrSave("-");
15869         gameInfo.white = StrSave("-");
15870         gameInfo.black = StrSave("-");
15871         break;
15872
15873       case IcsPlayingWhite:
15874       case IcsPlayingBlack:
15875       case IcsObserving:
15876       case IcsExamining:
15877         break;
15878
15879       case PlayFromGameFile:
15880         gameInfo.event = StrSave("Game from non-PGN file");
15881         gameInfo.site = StrSave(HostName());
15882         gameInfo.date = PGNDate();
15883         gameInfo.round = StrSave("-");
15884         gameInfo.white = StrSave("?");
15885         gameInfo.black = StrSave("?");
15886         break;
15887
15888       default:
15889         break;
15890     }
15891 }
15892
15893 void
15894 ReplaceComment (int index, char *text)
15895 {
15896     int len;
15897     char *p;
15898     float score;
15899
15900     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15901        pvInfoList[index-1].depth == len &&
15902        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15903        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15904     while (*text == '\n') text++;
15905     len = strlen(text);
15906     while (len > 0 && text[len - 1] == '\n') len--;
15907
15908     if (commentList[index] != NULL)
15909       free(commentList[index]);
15910
15911     if (len == 0) {
15912         commentList[index] = NULL;
15913         return;
15914     }
15915   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15916       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15917       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15918     commentList[index] = (char *) malloc(len + 2);
15919     strncpy(commentList[index], text, len);
15920     commentList[index][len] = '\n';
15921     commentList[index][len + 1] = NULLCHAR;
15922   } else {
15923     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15924     char *p;
15925     commentList[index] = (char *) malloc(len + 7);
15926     safeStrCpy(commentList[index], "{\n", 3);
15927     safeStrCpy(commentList[index]+2, text, len+1);
15928     commentList[index][len+2] = NULLCHAR;
15929     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15930     strcat(commentList[index], "\n}\n");
15931   }
15932 }
15933
15934 void
15935 CrushCRs (char *text)
15936 {
15937   char *p = text;
15938   char *q = text;
15939   char ch;
15940
15941   do {
15942     ch = *p++;
15943     if (ch == '\r') continue;
15944     *q++ = ch;
15945   } while (ch != '\0');
15946 }
15947
15948 void
15949 AppendComment (int index, char *text, Boolean addBraces)
15950 /* addBraces  tells if we should add {} */
15951 {
15952     int oldlen, len;
15953     char *old;
15954
15955 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15956     if(addBraces == 3) addBraces = 0; else // force appending literally
15957     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15958
15959     CrushCRs(text);
15960     while (*text == '\n') text++;
15961     len = strlen(text);
15962     while (len > 0 && text[len - 1] == '\n') len--;
15963     text[len] = NULLCHAR;
15964
15965     if (len == 0) return;
15966
15967     if (commentList[index] != NULL) {
15968       Boolean addClosingBrace = addBraces;
15969         old = commentList[index];
15970         oldlen = strlen(old);
15971         while(commentList[index][oldlen-1] ==  '\n')
15972           commentList[index][--oldlen] = NULLCHAR;
15973         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15974         safeStrCpy(commentList[index], old, oldlen + len + 6);
15975         free(old);
15976         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15977         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15978           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15979           while (*text == '\n') { text++; len--; }
15980           commentList[index][--oldlen] = NULLCHAR;
15981       }
15982         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15983         else          strcat(commentList[index], "\n");
15984         strcat(commentList[index], text);
15985         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15986         else          strcat(commentList[index], "\n");
15987     } else {
15988         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15989         if(addBraces)
15990           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15991         else commentList[index][0] = NULLCHAR;
15992         strcat(commentList[index], text);
15993         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15994         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15995     }
15996 }
15997
15998 static char *
15999 FindStr (char * text, char * sub_text)
16000 {
16001     char * result = strstr( text, sub_text );
16002
16003     if( result != NULL ) {
16004         result += strlen( sub_text );
16005     }
16006
16007     return result;
16008 }
16009
16010 /* [AS] Try to extract PV info from PGN comment */
16011 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16012 char *
16013 GetInfoFromComment (int index, char * text)
16014 {
16015     char * sep = text, *p;
16016
16017     if( text != NULL && index > 0 ) {
16018         int score = 0;
16019         int depth = 0;
16020         int time = -1, sec = 0, deci;
16021         char * s_eval = FindStr( text, "[%eval " );
16022         char * s_emt = FindStr( text, "[%emt " );
16023 #if 0
16024         if( s_eval != NULL || s_emt != NULL ) {
16025 #else
16026         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16027 #endif
16028             /* New style */
16029             char delim;
16030
16031             if( s_eval != NULL ) {
16032                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16033                     return text;
16034                 }
16035
16036                 if( delim != ']' ) {
16037                     return text;
16038                 }
16039             }
16040
16041             if( s_emt != NULL ) {
16042             }
16043                 return text;
16044         }
16045         else {
16046             /* We expect something like: [+|-]nnn.nn/dd */
16047             int score_lo = 0;
16048
16049             if(*text != '{') return text; // [HGM] braces: must be normal comment
16050
16051             sep = strchr( text, '/' );
16052             if( sep == NULL || sep < (text+4) ) {
16053                 return text;
16054             }
16055
16056             p = text;
16057             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16058             if(p[1] == '(') { // comment starts with PV
16059                p = strchr(p, ')'); // locate end of PV
16060                if(p == NULL || sep < p+5) return text;
16061                // at this point we have something like "{(.*) +0.23/6 ..."
16062                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16063                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16064                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16065             }
16066             time = -1; sec = -1; deci = -1;
16067             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16068                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16069                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16070                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16071                 return text;
16072             }
16073
16074             if( score_lo < 0 || score_lo >= 100 ) {
16075                 return text;
16076             }
16077
16078             if(sec >= 0) time = 600*time + 10*sec; else
16079             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16080
16081             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16082
16083             /* [HGM] PV time: now locate end of PV info */
16084             while( *++sep >= '0' && *sep <= '9'); // strip depth
16085             if(time >= 0)
16086             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16087             if(sec >= 0)
16088             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16089             if(deci >= 0)
16090             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16091             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16092         }
16093
16094         if( depth <= 0 ) {
16095             return text;
16096         }
16097
16098         if( time < 0 ) {
16099             time = -1;
16100         }
16101
16102         pvInfoList[index-1].depth = depth;
16103         pvInfoList[index-1].score = score;
16104         pvInfoList[index-1].time  = 10*time; // centi-sec
16105         if(*sep == '}') *sep = 0; else *--sep = '{';
16106         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16107     }
16108     return sep;
16109 }
16110
16111 void
16112 SendToProgram (char *message, ChessProgramState *cps)
16113 {
16114     int count, outCount, error;
16115     char buf[MSG_SIZ];
16116
16117     if (cps->pr == NoProc) return;
16118     Attention(cps);
16119
16120     if (appData.debugMode) {
16121         TimeMark now;
16122         GetTimeMark(&now);
16123         fprintf(debugFP, "%ld >%-6s: %s",
16124                 SubtractTimeMarks(&now, &programStartTime),
16125                 cps->which, message);
16126         if(serverFP)
16127             fprintf(serverFP, "%ld >%-6s: %s",
16128                 SubtractTimeMarks(&now, &programStartTime),
16129                 cps->which, message), fflush(serverFP);
16130     }
16131
16132     count = strlen(message);
16133     outCount = OutputToProcess(cps->pr, message, count, &error);
16134     if (outCount < count && !exiting
16135                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16136       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16137       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16138         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16139             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16140                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16141                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16142                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16143             } else {
16144                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16145                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16146                 gameInfo.result = res;
16147             }
16148             gameInfo.resultDetails = StrSave(buf);
16149         }
16150         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16151         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16152     }
16153 }
16154
16155 void
16156 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16157 {
16158     char *end_str;
16159     char buf[MSG_SIZ];
16160     ChessProgramState *cps = (ChessProgramState *)closure;
16161
16162     if (isr != cps->isr) return; /* Killed intentionally */
16163     if (count <= 0) {
16164         if (count == 0) {
16165             RemoveInputSource(cps->isr);
16166             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16167                     _(cps->which), cps->program);
16168             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16169             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16170                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16171                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16172                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16173                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16174                 } else {
16175                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16176                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16177                     gameInfo.result = res;
16178                 }
16179                 gameInfo.resultDetails = StrSave(buf);
16180             }
16181             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16182             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16183         } else {
16184             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16185                     _(cps->which), cps->program);
16186             RemoveInputSource(cps->isr);
16187
16188             /* [AS] Program is misbehaving badly... kill it */
16189             if( count == -2 ) {
16190                 DestroyChildProcess( cps->pr, 9 );
16191                 cps->pr = NoProc;
16192             }
16193
16194             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16195         }
16196         return;
16197     }
16198
16199     if ((end_str = strchr(message, '\r')) != NULL)
16200       *end_str = NULLCHAR;
16201     if ((end_str = strchr(message, '\n')) != NULL)
16202       *end_str = NULLCHAR;
16203
16204     if (appData.debugMode) {
16205         TimeMark now; int print = 1;
16206         char *quote = ""; char c; int i;
16207
16208         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16209                 char start = message[0];
16210                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16211                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16212                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16213                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16214                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16215                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16216                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16217                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16218                    sscanf(message, "hint: %c", &c)!=1 &&
16219                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16220                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16221                     print = (appData.engineComments >= 2);
16222                 }
16223                 message[0] = start; // restore original message
16224         }
16225         if(print) {
16226                 GetTimeMark(&now);
16227                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16228                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16229                         quote,
16230                         message);
16231                 if(serverFP)
16232                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16233                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16234                         quote,
16235                         message), fflush(serverFP);
16236         }
16237     }
16238
16239     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16240     if (appData.icsEngineAnalyze) {
16241         if (strstr(message, "whisper") != NULL ||
16242              strstr(message, "kibitz") != NULL ||
16243             strstr(message, "tellics") != NULL) return;
16244     }
16245
16246     HandleMachineMove(message, cps);
16247 }
16248
16249
16250 void
16251 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16252 {
16253     char buf[MSG_SIZ];
16254     int seconds;
16255
16256     if( timeControl_2 > 0 ) {
16257         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16258             tc = timeControl_2;
16259         }
16260     }
16261     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16262     inc /= cps->timeOdds;
16263     st  /= cps->timeOdds;
16264
16265     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16266
16267     if (st > 0) {
16268       /* Set exact time per move, normally using st command */
16269       if (cps->stKludge) {
16270         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16271         seconds = st % 60;
16272         if (seconds == 0) {
16273           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16274         } else {
16275           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16276         }
16277       } else {
16278         snprintf(buf, MSG_SIZ, "st %d\n", st);
16279       }
16280     } else {
16281       /* Set conventional or incremental time control, using level command */
16282       if (seconds == 0) {
16283         /* Note old gnuchess bug -- minutes:seconds used to not work.
16284            Fixed in later versions, but still avoid :seconds
16285            when seconds is 0. */
16286         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16287       } else {
16288         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16289                  seconds, inc/1000.);
16290       }
16291     }
16292     SendToProgram(buf, cps);
16293
16294     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16295     /* Orthogonally, limit search to given depth */
16296     if (sd > 0) {
16297       if (cps->sdKludge) {
16298         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16299       } else {
16300         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16301       }
16302       SendToProgram(buf, cps);
16303     }
16304
16305     if(cps->nps >= 0) { /* [HGM] nps */
16306         if(cps->supportsNPS == FALSE)
16307           cps->nps = -1; // don't use if engine explicitly says not supported!
16308         else {
16309           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16310           SendToProgram(buf, cps);
16311         }
16312     }
16313 }
16314
16315 ChessProgramState *
16316 WhitePlayer ()
16317 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16318 {
16319     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16320        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16321         return &second;
16322     return &first;
16323 }
16324
16325 void
16326 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16327 {
16328     char message[MSG_SIZ];
16329     long time, otime;
16330
16331     /* Note: this routine must be called when the clocks are stopped
16332        or when they have *just* been set or switched; otherwise
16333        it will be off by the time since the current tick started.
16334     */
16335     if (machineWhite) {
16336         time = whiteTimeRemaining / 10;
16337         otime = blackTimeRemaining / 10;
16338     } else {
16339         time = blackTimeRemaining / 10;
16340         otime = whiteTimeRemaining / 10;
16341     }
16342     /* [HGM] translate opponent's time by time-odds factor */
16343     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16344
16345     if (time <= 0) time = 1;
16346     if (otime <= 0) otime = 1;
16347
16348     snprintf(message, MSG_SIZ, "time %ld\n", time);
16349     SendToProgram(message, cps);
16350
16351     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16352     SendToProgram(message, cps);
16353 }
16354
16355 char *
16356 EngineDefinedVariant (ChessProgramState *cps, int n)
16357 {   // return name of n-th unknown variant that engine supports
16358     static char buf[MSG_SIZ];
16359     char *p, *s = cps->variants;
16360     if(!s) return NULL;
16361     do { // parse string from variants feature
16362       VariantClass v;
16363         p = strchr(s, ',');
16364         if(p) *p = NULLCHAR;
16365       v = StringToVariant(s);
16366       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16367         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16368             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16369         }
16370         if(p) *p++ = ',';
16371         if(n < 0) return buf;
16372     } while(s = p);
16373     return NULL;
16374 }
16375
16376 int
16377 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16378 {
16379   char buf[MSG_SIZ];
16380   int len = strlen(name);
16381   int val;
16382
16383   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16384     (*p) += len + 1;
16385     sscanf(*p, "%d", &val);
16386     *loc = (val != 0);
16387     while (**p && **p != ' ')
16388       (*p)++;
16389     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16390     SendToProgram(buf, cps);
16391     return TRUE;
16392   }
16393   return FALSE;
16394 }
16395
16396 int
16397 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16398 {
16399   char buf[MSG_SIZ];
16400   int len = strlen(name);
16401   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16402     (*p) += len + 1;
16403     sscanf(*p, "%d", loc);
16404     while (**p && **p != ' ') (*p)++;
16405     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16406     SendToProgram(buf, cps);
16407     return TRUE;
16408   }
16409   return FALSE;
16410 }
16411
16412 int
16413 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16414 {
16415   char buf[MSG_SIZ];
16416   int len = strlen(name);
16417   if (strncmp((*p), name, len) == 0
16418       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16419     (*p) += len + 2;
16420     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16421     sscanf(*p, "%[^\"]", *loc);
16422     while (**p && **p != '\"') (*p)++;
16423     if (**p == '\"') (*p)++;
16424     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16425     SendToProgram(buf, cps);
16426     return TRUE;
16427   }
16428   return FALSE;
16429 }
16430
16431 int
16432 ParseOption (Option *opt, ChessProgramState *cps)
16433 // [HGM] options: process the string that defines an engine option, and determine
16434 // name, type, default value, and allowed value range
16435 {
16436         char *p, *q, buf[MSG_SIZ];
16437         int n, min = (-1)<<31, max = 1<<31, def;
16438
16439         if(p = strstr(opt->name, " -spin ")) {
16440             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16441             if(max < min) max = min; // enforce consistency
16442             if(def < min) def = min;
16443             if(def > max) def = max;
16444             opt->value = def;
16445             opt->min = min;
16446             opt->max = max;
16447             opt->type = Spin;
16448         } else if((p = strstr(opt->name, " -slider "))) {
16449             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16450             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16451             if(max < min) max = min; // enforce consistency
16452             if(def < min) def = min;
16453             if(def > max) def = max;
16454             opt->value = def;
16455             opt->min = min;
16456             opt->max = max;
16457             opt->type = Spin; // Slider;
16458         } else if((p = strstr(opt->name, " -string "))) {
16459             opt->textValue = p+9;
16460             opt->type = TextBox;
16461         } else if((p = strstr(opt->name, " -file "))) {
16462             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16463             opt->textValue = p+7;
16464             opt->type = FileName; // FileName;
16465         } else if((p = strstr(opt->name, " -path "))) {
16466             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16467             opt->textValue = p+7;
16468             opt->type = PathName; // PathName;
16469         } else if(p = strstr(opt->name, " -check ")) {
16470             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16471             opt->value = (def != 0);
16472             opt->type = CheckBox;
16473         } else if(p = strstr(opt->name, " -combo ")) {
16474             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16475             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16476             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16477             opt->value = n = 0;
16478             while(q = StrStr(q, " /// ")) {
16479                 n++; *q = 0;    // count choices, and null-terminate each of them
16480                 q += 5;
16481                 if(*q == '*') { // remember default, which is marked with * prefix
16482                     q++;
16483                     opt->value = n;
16484                 }
16485                 cps->comboList[cps->comboCnt++] = q;
16486             }
16487             cps->comboList[cps->comboCnt++] = NULL;
16488             opt->max = n + 1;
16489             opt->type = ComboBox;
16490         } else if(p = strstr(opt->name, " -button")) {
16491             opt->type = Button;
16492         } else if(p = strstr(opt->name, " -save")) {
16493             opt->type = SaveButton;
16494         } else return FALSE;
16495         *p = 0; // terminate option name
16496         // now look if the command-line options define a setting for this engine option.
16497         if(cps->optionSettings && cps->optionSettings[0])
16498             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16499         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16500           snprintf(buf, MSG_SIZ, "option %s", p);
16501                 if(p = strstr(buf, ",")) *p = 0;
16502                 if(q = strchr(buf, '=')) switch(opt->type) {
16503                     case ComboBox:
16504                         for(n=0; n<opt->max; n++)
16505                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16506                         break;
16507                     case TextBox:
16508                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16509                         break;
16510                     case Spin:
16511                     case CheckBox:
16512                         opt->value = atoi(q+1);
16513                     default:
16514                         break;
16515                 }
16516                 strcat(buf, "\n");
16517                 SendToProgram(buf, cps);
16518         }
16519         return TRUE;
16520 }
16521
16522 void
16523 FeatureDone (ChessProgramState *cps, int val)
16524 {
16525   DelayedEventCallback cb = GetDelayedEvent();
16526   if ((cb == InitBackEnd3 && cps == &first) ||
16527       (cb == SettingsMenuIfReady && cps == &second) ||
16528       (cb == LoadEngine) ||
16529       (cb == TwoMachinesEventIfReady)) {
16530     CancelDelayedEvent();
16531     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16532   }
16533   cps->initDone = val;
16534   if(val) cps->reload = FALSE;
16535 }
16536
16537 /* Parse feature command from engine */
16538 void
16539 ParseFeatures (char *args, ChessProgramState *cps)
16540 {
16541   char *p = args;
16542   char *q = NULL;
16543   int val;
16544   char buf[MSG_SIZ];
16545
16546   for (;;) {
16547     while (*p == ' ') p++;
16548     if (*p == NULLCHAR) return;
16549
16550     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16551     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16552     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16553     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16554     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16555     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16556     if (BoolFeature(&p, "reuse", &val, cps)) {
16557       /* Engine can disable reuse, but can't enable it if user said no */
16558       if (!val) cps->reuse = FALSE;
16559       continue;
16560     }
16561     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16562     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16563       if (gameMode == TwoMachinesPlay) {
16564         DisplayTwoMachinesTitle();
16565       } else {
16566         DisplayTitle("");
16567       }
16568       continue;
16569     }
16570     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16571     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16572     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16573     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16574     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16575     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16576     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16577     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16578     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16579     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16580     if (IntFeature(&p, "done", &val, cps)) {
16581       FeatureDone(cps, val);
16582       continue;
16583     }
16584     /* Added by Tord: */
16585     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16586     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16587     /* End of additions by Tord */
16588
16589     /* [HGM] added features: */
16590     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16591     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16592     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16593     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16594     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16595     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16596     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16597     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16598         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16599         FREE(cps->option[cps->nrOptions].name);
16600         cps->option[cps->nrOptions].name = q; q = NULL;
16601         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16602           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16603             SendToProgram(buf, cps);
16604             continue;
16605         }
16606         if(cps->nrOptions >= MAX_OPTIONS) {
16607             cps->nrOptions--;
16608             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16609             DisplayError(buf, 0);
16610         }
16611         continue;
16612     }
16613     /* End of additions by HGM */
16614
16615     /* unknown feature: complain and skip */
16616     q = p;
16617     while (*q && *q != '=') q++;
16618     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16619     SendToProgram(buf, cps);
16620     p = q;
16621     if (*p == '=') {
16622       p++;
16623       if (*p == '\"') {
16624         p++;
16625         while (*p && *p != '\"') p++;
16626         if (*p == '\"') p++;
16627       } else {
16628         while (*p && *p != ' ') p++;
16629       }
16630     }
16631   }
16632
16633 }
16634
16635 void
16636 PeriodicUpdatesEvent (int newState)
16637 {
16638     if (newState == appData.periodicUpdates)
16639       return;
16640
16641     appData.periodicUpdates=newState;
16642
16643     /* Display type changes, so update it now */
16644 //    DisplayAnalysis();
16645
16646     /* Get the ball rolling again... */
16647     if (newState) {
16648         AnalysisPeriodicEvent(1);
16649         StartAnalysisClock();
16650     }
16651 }
16652
16653 void
16654 PonderNextMoveEvent (int newState)
16655 {
16656     if (newState == appData.ponderNextMove) return;
16657     if (gameMode == EditPosition) EditPositionDone(TRUE);
16658     if (newState) {
16659         SendToProgram("hard\n", &first);
16660         if (gameMode == TwoMachinesPlay) {
16661             SendToProgram("hard\n", &second);
16662         }
16663     } else {
16664         SendToProgram("easy\n", &first);
16665         thinkOutput[0] = NULLCHAR;
16666         if (gameMode == TwoMachinesPlay) {
16667             SendToProgram("easy\n", &second);
16668         }
16669     }
16670     appData.ponderNextMove = newState;
16671 }
16672
16673 void
16674 NewSettingEvent (int option, int *feature, char *command, int value)
16675 {
16676     char buf[MSG_SIZ];
16677
16678     if (gameMode == EditPosition) EditPositionDone(TRUE);
16679     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16680     if(feature == NULL || *feature) SendToProgram(buf, &first);
16681     if (gameMode == TwoMachinesPlay) {
16682         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16683     }
16684 }
16685
16686 void
16687 ShowThinkingEvent ()
16688 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16689 {
16690     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16691     int newState = appData.showThinking
16692         // [HGM] thinking: other features now need thinking output as well
16693         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16694
16695     if (oldState == newState) return;
16696     oldState = newState;
16697     if (gameMode == EditPosition) EditPositionDone(TRUE);
16698     if (oldState) {
16699         SendToProgram("post\n", &first);
16700         if (gameMode == TwoMachinesPlay) {
16701             SendToProgram("post\n", &second);
16702         }
16703     } else {
16704         SendToProgram("nopost\n", &first);
16705         thinkOutput[0] = NULLCHAR;
16706         if (gameMode == TwoMachinesPlay) {
16707             SendToProgram("nopost\n", &second);
16708         }
16709     }
16710 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16711 }
16712
16713 void
16714 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16715 {
16716   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16717   if (pr == NoProc) return;
16718   AskQuestion(title, question, replyPrefix, pr);
16719 }
16720
16721 void
16722 TypeInEvent (char firstChar)
16723 {
16724     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16725         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16726         gameMode == AnalyzeMode || gameMode == EditGame ||
16727         gameMode == EditPosition || gameMode == IcsExamining ||
16728         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16729         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16730                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16731                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16732         gameMode == Training) PopUpMoveDialog(firstChar);
16733 }
16734
16735 void
16736 TypeInDoneEvent (char *move)
16737 {
16738         Board board;
16739         int n, fromX, fromY, toX, toY;
16740         char promoChar;
16741         ChessMove moveType;
16742
16743         // [HGM] FENedit
16744         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16745                 EditPositionPasteFEN(move);
16746                 return;
16747         }
16748         // [HGM] movenum: allow move number to be typed in any mode
16749         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16750           ToNrEvent(2*n-1);
16751           return;
16752         }
16753         // undocumented kludge: allow command-line option to be typed in!
16754         // (potentially fatal, and does not implement the effect of the option.)
16755         // should only be used for options that are values on which future decisions will be made,
16756         // and definitely not on options that would be used during initialization.
16757         if(strstr(move, "!!! -") == move) {
16758             ParseArgsFromString(move+4);
16759             return;
16760         }
16761
16762       if (gameMode != EditGame && currentMove != forwardMostMove &&
16763         gameMode != Training) {
16764         DisplayMoveError(_("Displayed move is not current"));
16765       } else {
16766         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16767           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16768         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16769         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16770           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16771           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16772         } else {
16773           DisplayMoveError(_("Could not parse move"));
16774         }
16775       }
16776 }
16777
16778 void
16779 DisplayMove (int moveNumber)
16780 {
16781     char message[MSG_SIZ];
16782     char res[MSG_SIZ];
16783     char cpThinkOutput[MSG_SIZ];
16784
16785     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16786
16787     if (moveNumber == forwardMostMove - 1 ||
16788         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16789
16790         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16791
16792         if (strchr(cpThinkOutput, '\n')) {
16793             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16794         }
16795     } else {
16796         *cpThinkOutput = NULLCHAR;
16797     }
16798
16799     /* [AS] Hide thinking from human user */
16800     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16801         *cpThinkOutput = NULLCHAR;
16802         if( thinkOutput[0] != NULLCHAR ) {
16803             int i;
16804
16805             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16806                 cpThinkOutput[i] = '.';
16807             }
16808             cpThinkOutput[i] = NULLCHAR;
16809             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16810         }
16811     }
16812
16813     if (moveNumber == forwardMostMove - 1 &&
16814         gameInfo.resultDetails != NULL) {
16815         if (gameInfo.resultDetails[0] == NULLCHAR) {
16816           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16817         } else {
16818           snprintf(res, MSG_SIZ, " {%s} %s",
16819                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16820         }
16821     } else {
16822         res[0] = NULLCHAR;
16823     }
16824
16825     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16826         DisplayMessage(res, cpThinkOutput);
16827     } else {
16828       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16829                 WhiteOnMove(moveNumber) ? " " : ".. ",
16830                 parseList[moveNumber], res);
16831         DisplayMessage(message, cpThinkOutput);
16832     }
16833 }
16834
16835 void
16836 DisplayComment (int moveNumber, char *text)
16837 {
16838     char title[MSG_SIZ];
16839
16840     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16841       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16842     } else {
16843       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16844               WhiteOnMove(moveNumber) ? " " : ".. ",
16845               parseList[moveNumber]);
16846     }
16847     if (text != NULL && (appData.autoDisplayComment || commentUp))
16848         CommentPopUp(title, text);
16849 }
16850
16851 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16852  * might be busy thinking or pondering.  It can be omitted if your
16853  * gnuchess is configured to stop thinking immediately on any user
16854  * input.  However, that gnuchess feature depends on the FIONREAD
16855  * ioctl, which does not work properly on some flavors of Unix.
16856  */
16857 void
16858 Attention (ChessProgramState *cps)
16859 {
16860 #if ATTENTION
16861     if (!cps->useSigint) return;
16862     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16863     switch (gameMode) {
16864       case MachinePlaysWhite:
16865       case MachinePlaysBlack:
16866       case TwoMachinesPlay:
16867       case IcsPlayingWhite:
16868       case IcsPlayingBlack:
16869       case AnalyzeMode:
16870       case AnalyzeFile:
16871         /* Skip if we know it isn't thinking */
16872         if (!cps->maybeThinking) return;
16873         if (appData.debugMode)
16874           fprintf(debugFP, "Interrupting %s\n", cps->which);
16875         InterruptChildProcess(cps->pr);
16876         cps->maybeThinking = FALSE;
16877         break;
16878       default:
16879         break;
16880     }
16881 #endif /*ATTENTION*/
16882 }
16883
16884 int
16885 CheckFlags ()
16886 {
16887     if (whiteTimeRemaining <= 0) {
16888         if (!whiteFlag) {
16889             whiteFlag = TRUE;
16890             if (appData.icsActive) {
16891                 if (appData.autoCallFlag &&
16892                     gameMode == IcsPlayingBlack && !blackFlag) {
16893                   SendToICS(ics_prefix);
16894                   SendToICS("flag\n");
16895                 }
16896             } else {
16897                 if (blackFlag) {
16898                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16899                 } else {
16900                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16901                     if (appData.autoCallFlag) {
16902                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16903                         return TRUE;
16904                     }
16905                 }
16906             }
16907         }
16908     }
16909     if (blackTimeRemaining <= 0) {
16910         if (!blackFlag) {
16911             blackFlag = TRUE;
16912             if (appData.icsActive) {
16913                 if (appData.autoCallFlag &&
16914                     gameMode == IcsPlayingWhite && !whiteFlag) {
16915                   SendToICS(ics_prefix);
16916                   SendToICS("flag\n");
16917                 }
16918             } else {
16919                 if (whiteFlag) {
16920                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16921                 } else {
16922                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16923                     if (appData.autoCallFlag) {
16924                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16925                         return TRUE;
16926                     }
16927                 }
16928             }
16929         }
16930     }
16931     return FALSE;
16932 }
16933
16934 void
16935 CheckTimeControl ()
16936 {
16937     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16938         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16939
16940     /*
16941      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16942      */
16943     if ( !WhiteOnMove(forwardMostMove) ) {
16944         /* White made time control */
16945         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16946         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16947         /* [HGM] time odds: correct new time quota for time odds! */
16948                                             / WhitePlayer()->timeOdds;
16949         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16950     } else {
16951         lastBlack -= blackTimeRemaining;
16952         /* Black made time control */
16953         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16954                                             / WhitePlayer()->other->timeOdds;
16955         lastWhite = whiteTimeRemaining;
16956     }
16957 }
16958
16959 void
16960 DisplayBothClocks ()
16961 {
16962     int wom = gameMode == EditPosition ?
16963       !blackPlaysFirst : WhiteOnMove(currentMove);
16964     DisplayWhiteClock(whiteTimeRemaining, wom);
16965     DisplayBlackClock(blackTimeRemaining, !wom);
16966 }
16967
16968
16969 /* Timekeeping seems to be a portability nightmare.  I think everyone
16970    has ftime(), but I'm really not sure, so I'm including some ifdefs
16971    to use other calls if you don't.  Clocks will be less accurate if
16972    you have neither ftime nor gettimeofday.
16973 */
16974
16975 /* VS 2008 requires the #include outside of the function */
16976 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16977 #include <sys/timeb.h>
16978 #endif
16979
16980 /* Get the current time as a TimeMark */
16981 void
16982 GetTimeMark (TimeMark *tm)
16983 {
16984 #if HAVE_GETTIMEOFDAY
16985
16986     struct timeval timeVal;
16987     struct timezone timeZone;
16988
16989     gettimeofday(&timeVal, &timeZone);
16990     tm->sec = (long) timeVal.tv_sec;
16991     tm->ms = (int) (timeVal.tv_usec / 1000L);
16992
16993 #else /*!HAVE_GETTIMEOFDAY*/
16994 #if HAVE_FTIME
16995
16996 // include <sys/timeb.h> / moved to just above start of function
16997     struct timeb timeB;
16998
16999     ftime(&timeB);
17000     tm->sec = (long) timeB.time;
17001     tm->ms = (int) timeB.millitm;
17002
17003 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17004     tm->sec = (long) time(NULL);
17005     tm->ms = 0;
17006 #endif
17007 #endif
17008 }
17009
17010 /* Return the difference in milliseconds between two
17011    time marks.  We assume the difference will fit in a long!
17012 */
17013 long
17014 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17015 {
17016     return 1000L*(tm2->sec - tm1->sec) +
17017            (long) (tm2->ms - tm1->ms);
17018 }
17019
17020
17021 /*
17022  * Code to manage the game clocks.
17023  *
17024  * In tournament play, black starts the clock and then white makes a move.
17025  * We give the human user a slight advantage if he is playing white---the
17026  * clocks don't run until he makes his first move, so it takes zero time.
17027  * Also, we don't account for network lag, so we could get out of sync
17028  * with GNU Chess's clock -- but then, referees are always right.
17029  */
17030
17031 static TimeMark tickStartTM;
17032 static long intendedTickLength;
17033
17034 long
17035 NextTickLength (long timeRemaining)
17036 {
17037     long nominalTickLength, nextTickLength;
17038
17039     if (timeRemaining > 0L && timeRemaining <= 10000L)
17040       nominalTickLength = 100L;
17041     else
17042       nominalTickLength = 1000L;
17043     nextTickLength = timeRemaining % nominalTickLength;
17044     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17045
17046     return nextTickLength;
17047 }
17048
17049 /* Adjust clock one minute up or down */
17050 void
17051 AdjustClock (Boolean which, int dir)
17052 {
17053     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17054     if(which) blackTimeRemaining += 60000*dir;
17055     else      whiteTimeRemaining += 60000*dir;
17056     DisplayBothClocks();
17057     adjustedClock = TRUE;
17058 }
17059
17060 /* Stop clocks and reset to a fresh time control */
17061 void
17062 ResetClocks ()
17063 {
17064     (void) StopClockTimer();
17065     if (appData.icsActive) {
17066         whiteTimeRemaining = blackTimeRemaining = 0;
17067     } else if (searchTime) {
17068         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17069         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17070     } else { /* [HGM] correct new time quote for time odds */
17071         whiteTC = blackTC = fullTimeControlString;
17072         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17073         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17074     }
17075     if (whiteFlag || blackFlag) {
17076         DisplayTitle("");
17077         whiteFlag = blackFlag = FALSE;
17078     }
17079     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17080     DisplayBothClocks();
17081     adjustedClock = FALSE;
17082 }
17083
17084 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17085
17086 /* Decrement running clock by amount of time that has passed */
17087 void
17088 DecrementClocks ()
17089 {
17090     long timeRemaining;
17091     long lastTickLength, fudge;
17092     TimeMark now;
17093
17094     if (!appData.clockMode) return;
17095     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17096
17097     GetTimeMark(&now);
17098
17099     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17100
17101     /* Fudge if we woke up a little too soon */
17102     fudge = intendedTickLength - lastTickLength;
17103     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17104
17105     if (WhiteOnMove(forwardMostMove)) {
17106         if(whiteNPS >= 0) lastTickLength = 0;
17107         timeRemaining = whiteTimeRemaining -= lastTickLength;
17108         if(timeRemaining < 0 && !appData.icsActive) {
17109             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17110             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17111                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17112                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17113             }
17114         }
17115         DisplayWhiteClock(whiteTimeRemaining - fudge,
17116                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17117     } else {
17118         if(blackNPS >= 0) lastTickLength = 0;
17119         timeRemaining = blackTimeRemaining -= lastTickLength;
17120         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17121             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17122             if(suddenDeath) {
17123                 blackStartMove = forwardMostMove;
17124                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17125             }
17126         }
17127         DisplayBlackClock(blackTimeRemaining - fudge,
17128                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17129     }
17130     if (CheckFlags()) return;
17131
17132     if(twoBoards) { // count down secondary board's clocks as well
17133         activePartnerTime -= lastTickLength;
17134         partnerUp = 1;
17135         if(activePartner == 'W')
17136             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17137         else
17138             DisplayBlackClock(activePartnerTime, TRUE);
17139         partnerUp = 0;
17140     }
17141
17142     tickStartTM = now;
17143     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17144     StartClockTimer(intendedTickLength);
17145
17146     /* if the time remaining has fallen below the alarm threshold, sound the
17147      * alarm. if the alarm has sounded and (due to a takeback or time control
17148      * with increment) the time remaining has increased to a level above the
17149      * threshold, reset the alarm so it can sound again.
17150      */
17151
17152     if (appData.icsActive && appData.icsAlarm) {
17153
17154         /* make sure we are dealing with the user's clock */
17155         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17156                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17157            )) return;
17158
17159         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17160             alarmSounded = FALSE;
17161         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17162             PlayAlarmSound();
17163             alarmSounded = TRUE;
17164         }
17165     }
17166 }
17167
17168
17169 /* A player has just moved, so stop the previously running
17170    clock and (if in clock mode) start the other one.
17171    We redisplay both clocks in case we're in ICS mode, because
17172    ICS gives us an update to both clocks after every move.
17173    Note that this routine is called *after* forwardMostMove
17174    is updated, so the last fractional tick must be subtracted
17175    from the color that is *not* on move now.
17176 */
17177 void
17178 SwitchClocks (int newMoveNr)
17179 {
17180     long lastTickLength;
17181     TimeMark now;
17182     int flagged = FALSE;
17183
17184     GetTimeMark(&now);
17185
17186     if (StopClockTimer() && appData.clockMode) {
17187         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17188         if (!WhiteOnMove(forwardMostMove)) {
17189             if(blackNPS >= 0) lastTickLength = 0;
17190             blackTimeRemaining -= lastTickLength;
17191            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17192 //         if(pvInfoList[forwardMostMove].time == -1)
17193                  pvInfoList[forwardMostMove].time =               // use GUI time
17194                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17195         } else {
17196            if(whiteNPS >= 0) lastTickLength = 0;
17197            whiteTimeRemaining -= lastTickLength;
17198            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17199 //         if(pvInfoList[forwardMostMove].time == -1)
17200                  pvInfoList[forwardMostMove].time =
17201                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17202         }
17203         flagged = CheckFlags();
17204     }
17205     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17206     CheckTimeControl();
17207
17208     if (flagged || !appData.clockMode) return;
17209
17210     switch (gameMode) {
17211       case MachinePlaysBlack:
17212       case MachinePlaysWhite:
17213       case BeginningOfGame:
17214         if (pausing) return;
17215         break;
17216
17217       case EditGame:
17218       case PlayFromGameFile:
17219       case IcsExamining:
17220         return;
17221
17222       default:
17223         break;
17224     }
17225
17226     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17227         if(WhiteOnMove(forwardMostMove))
17228              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17229         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17230     }
17231
17232     tickStartTM = now;
17233     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17234       whiteTimeRemaining : blackTimeRemaining);
17235     StartClockTimer(intendedTickLength);
17236 }
17237
17238
17239 /* Stop both clocks */
17240 void
17241 StopClocks ()
17242 {
17243     long lastTickLength;
17244     TimeMark now;
17245
17246     if (!StopClockTimer()) return;
17247     if (!appData.clockMode) return;
17248
17249     GetTimeMark(&now);
17250
17251     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17252     if (WhiteOnMove(forwardMostMove)) {
17253         if(whiteNPS >= 0) lastTickLength = 0;
17254         whiteTimeRemaining -= lastTickLength;
17255         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17256     } else {
17257         if(blackNPS >= 0) lastTickLength = 0;
17258         blackTimeRemaining -= lastTickLength;
17259         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17260     }
17261     CheckFlags();
17262 }
17263
17264 /* Start clock of player on move.  Time may have been reset, so
17265    if clock is already running, stop and restart it. */
17266 void
17267 StartClocks ()
17268 {
17269     (void) StopClockTimer(); /* in case it was running already */
17270     DisplayBothClocks();
17271     if (CheckFlags()) return;
17272
17273     if (!appData.clockMode) return;
17274     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17275
17276     GetTimeMark(&tickStartTM);
17277     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17278       whiteTimeRemaining : blackTimeRemaining);
17279
17280    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17281     whiteNPS = blackNPS = -1;
17282     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17283        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17284         whiteNPS = first.nps;
17285     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17286        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17287         blackNPS = first.nps;
17288     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17289         whiteNPS = second.nps;
17290     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17291         blackNPS = second.nps;
17292     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17293
17294     StartClockTimer(intendedTickLength);
17295 }
17296
17297 char *
17298 TimeString (long ms)
17299 {
17300     long second, minute, hour, day;
17301     char *sign = "";
17302     static char buf[32];
17303
17304     if (ms > 0 && ms <= 9900) {
17305       /* convert milliseconds to tenths, rounding up */
17306       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17307
17308       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17309       return buf;
17310     }
17311
17312     /* convert milliseconds to seconds, rounding up */
17313     /* use floating point to avoid strangeness of integer division
17314        with negative dividends on many machines */
17315     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17316
17317     if (second < 0) {
17318         sign = "-";
17319         second = -second;
17320     }
17321
17322     day = second / (60 * 60 * 24);
17323     second = second % (60 * 60 * 24);
17324     hour = second / (60 * 60);
17325     second = second % (60 * 60);
17326     minute = second / 60;
17327     second = second % 60;
17328
17329     if (day > 0)
17330       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17331               sign, day, hour, minute, second);
17332     else if (hour > 0)
17333       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17334     else
17335       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17336
17337     return buf;
17338 }
17339
17340
17341 /*
17342  * This is necessary because some C libraries aren't ANSI C compliant yet.
17343  */
17344 char *
17345 StrStr (char *string, char *match)
17346 {
17347     int i, length;
17348
17349     length = strlen(match);
17350
17351     for (i = strlen(string) - length; i >= 0; i--, string++)
17352       if (!strncmp(match, string, length))
17353         return string;
17354
17355     return NULL;
17356 }
17357
17358 char *
17359 StrCaseStr (char *string, char *match)
17360 {
17361     int i, j, length;
17362
17363     length = strlen(match);
17364
17365     for (i = strlen(string) - length; i >= 0; i--, string++) {
17366         for (j = 0; j < length; j++) {
17367             if (ToLower(match[j]) != ToLower(string[j]))
17368               break;
17369         }
17370         if (j == length) return string;
17371     }
17372
17373     return NULL;
17374 }
17375
17376 #ifndef _amigados
17377 int
17378 StrCaseCmp (char *s1, char *s2)
17379 {
17380     char c1, c2;
17381
17382     for (;;) {
17383         c1 = ToLower(*s1++);
17384         c2 = ToLower(*s2++);
17385         if (c1 > c2) return 1;
17386         if (c1 < c2) return -1;
17387         if (c1 == NULLCHAR) return 0;
17388     }
17389 }
17390
17391
17392 int
17393 ToLower (int c)
17394 {
17395     return isupper(c) ? tolower(c) : c;
17396 }
17397
17398
17399 int
17400 ToUpper (int c)
17401 {
17402     return islower(c) ? toupper(c) : c;
17403 }
17404 #endif /* !_amigados    */
17405
17406 char *
17407 StrSave (char *s)
17408 {
17409   char *ret;
17410
17411   if ((ret = (char *) malloc(strlen(s) + 1)))
17412     {
17413       safeStrCpy(ret, s, strlen(s)+1);
17414     }
17415   return ret;
17416 }
17417
17418 char *
17419 StrSavePtr (char *s, char **savePtr)
17420 {
17421     if (*savePtr) {
17422         free(*savePtr);
17423     }
17424     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17425       safeStrCpy(*savePtr, s, strlen(s)+1);
17426     }
17427     return(*savePtr);
17428 }
17429
17430 char *
17431 PGNDate ()
17432 {
17433     time_t clock;
17434     struct tm *tm;
17435     char buf[MSG_SIZ];
17436
17437     clock = time((time_t *)NULL);
17438     tm = localtime(&clock);
17439     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17440             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17441     return StrSave(buf);
17442 }
17443
17444
17445 char *
17446 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17447 {
17448     int i, j, fromX, fromY, toX, toY;
17449     int whiteToPlay;
17450     char buf[MSG_SIZ];
17451     char *p, *q;
17452     int emptycount;
17453     ChessSquare piece;
17454
17455     whiteToPlay = (gameMode == EditPosition) ?
17456       !blackPlaysFirst : (move % 2 == 0);
17457     p = buf;
17458
17459     /* Piece placement data */
17460     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17461         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17462         emptycount = 0;
17463         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17464             if (boards[move][i][j] == EmptySquare) {
17465                 emptycount++;
17466             } else { ChessSquare piece = boards[move][i][j];
17467                 if (emptycount > 0) {
17468                     if(emptycount<10) /* [HGM] can be >= 10 */
17469                         *p++ = '0' + emptycount;
17470                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17471                     emptycount = 0;
17472                 }
17473                 if(PieceToChar(piece) == '+') {
17474                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17475                     *p++ = '+';
17476                     piece = (ChessSquare)(DEMOTED piece);
17477                 }
17478                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17479                 if(p[-1] == '~') {
17480                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17481                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17482                     *p++ = '~';
17483                 }
17484             }
17485         }
17486         if (emptycount > 0) {
17487             if(emptycount<10) /* [HGM] can be >= 10 */
17488                 *p++ = '0' + emptycount;
17489             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17490             emptycount = 0;
17491         }
17492         *p++ = '/';
17493     }
17494     *(p - 1) = ' ';
17495
17496     /* [HGM] print Crazyhouse or Shogi holdings */
17497     if( gameInfo.holdingsWidth ) {
17498         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17499         q = p;
17500         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17501             piece = boards[move][i][BOARD_WIDTH-1];
17502             if( piece != EmptySquare )
17503               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17504                   *p++ = PieceToChar(piece);
17505         }
17506         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17507             piece = boards[move][BOARD_HEIGHT-i-1][0];
17508             if( piece != EmptySquare )
17509               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17510                   *p++ = PieceToChar(piece);
17511         }
17512
17513         if( q == p ) *p++ = '-';
17514         *p++ = ']';
17515         *p++ = ' ';
17516     }
17517
17518     /* Active color */
17519     *p++ = whiteToPlay ? 'w' : 'b';
17520     *p++ = ' ';
17521
17522   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17523     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17524   } else {
17525   if(nrCastlingRights) {
17526      q = p;
17527      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17528        /* [HGM] write directly from rights */
17529            if(boards[move][CASTLING][2] != NoRights &&
17530               boards[move][CASTLING][0] != NoRights   )
17531                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17532            if(boards[move][CASTLING][2] != NoRights &&
17533               boards[move][CASTLING][1] != NoRights   )
17534                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17535            if(boards[move][CASTLING][5] != NoRights &&
17536               boards[move][CASTLING][3] != NoRights   )
17537                 *p++ = boards[move][CASTLING][3] + AAA;
17538            if(boards[move][CASTLING][5] != NoRights &&
17539               boards[move][CASTLING][4] != NoRights   )
17540                 *p++ = boards[move][CASTLING][4] + AAA;
17541      } else {
17542
17543         /* [HGM] write true castling rights */
17544         if( nrCastlingRights == 6 ) {
17545             int q, k=0;
17546             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17547                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17548             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17549                  boards[move][CASTLING][2] != NoRights  );
17550             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17551                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17552                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17553                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17554                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17555             }
17556             if(q) *p++ = 'Q';
17557             k = 0;
17558             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17559                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17560             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17561                  boards[move][CASTLING][5] != NoRights  );
17562             if(gameInfo.variant == VariantSChess) {
17563                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17564                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17565                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17566                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17567             }
17568             if(q) *p++ = 'q';
17569         }
17570      }
17571      if (q == p) *p++ = '-'; /* No castling rights */
17572      *p++ = ' ';
17573   }
17574
17575   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17576      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17577      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17578     /* En passant target square */
17579     if (move > backwardMostMove) {
17580         fromX = moveList[move - 1][0] - AAA;
17581         fromY = moveList[move - 1][1] - ONE;
17582         toX = moveList[move - 1][2] - AAA;
17583         toY = moveList[move - 1][3] - ONE;
17584         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17585             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17586             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17587             fromX == toX) {
17588             /* 2-square pawn move just happened */
17589             *p++ = toX + AAA;
17590             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17591         } else {
17592             *p++ = '-';
17593         }
17594     } else if(move == backwardMostMove) {
17595         // [HGM] perhaps we should always do it like this, and forget the above?
17596         if((signed char)boards[move][EP_STATUS] >= 0) {
17597             *p++ = boards[move][EP_STATUS] + AAA;
17598             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17599         } else {
17600             *p++ = '-';
17601         }
17602     } else {
17603         *p++ = '-';
17604     }
17605     *p++ = ' ';
17606   }
17607   }
17608
17609     if(moveCounts)
17610     {   int i = 0, j=move;
17611
17612         /* [HGM] find reversible plies */
17613         if (appData.debugMode) { int k;
17614             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17615             for(k=backwardMostMove; k<=forwardMostMove; k++)
17616                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17617
17618         }
17619
17620         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17621         if( j == backwardMostMove ) i += initialRulePlies;
17622         sprintf(p, "%d ", i);
17623         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17624
17625         /* Fullmove number */
17626         sprintf(p, "%d", (move / 2) + 1);
17627     } else *--p = NULLCHAR;
17628
17629     return StrSave(buf);
17630 }
17631
17632 Boolean
17633 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17634 {
17635     int i, j, k, w=0;
17636     char *p, c;
17637     int emptycount, virgin[BOARD_FILES];
17638     ChessSquare piece;
17639
17640     p = fen;
17641
17642     /* Piece placement data */
17643     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17644         j = 0;
17645         for (;;) {
17646             if (*p == '/' || *p == ' ' || *p == '[' ) {
17647                 if(j > w) w = j;
17648                 emptycount = gameInfo.boardWidth - j;
17649                 while (emptycount--)
17650                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17651                 if (*p == '/') p++;
17652                 else if(autoSize) { // we stumbled unexpectedly into end of board
17653                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17654                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17655                     }
17656                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17657                 }
17658                 break;
17659 #if(BOARD_FILES >= 10)
17660             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17661                 p++; emptycount=10;
17662                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17663                 while (emptycount--)
17664                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17665 #endif
17666             } else if (*p == '*') {
17667                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17668             } else if (isdigit(*p)) {
17669                 emptycount = *p++ - '0';
17670                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17671                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17672                 while (emptycount--)
17673                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17674             } else if (*p == '+' || isalpha(*p)) {
17675                 if (j >= gameInfo.boardWidth) return FALSE;
17676                 if(*p=='+') {
17677                     piece = CharToPiece(*++p);
17678                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17679                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17680                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17681                 } else piece = CharToPiece(*p++);
17682
17683                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17684                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17685                     piece = (ChessSquare) (PROMOTED piece);
17686                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17687                     p++;
17688                 }
17689                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17690             } else {
17691                 return FALSE;
17692             }
17693         }
17694     }
17695     while (*p == '/' || *p == ' ') p++;
17696
17697     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17698
17699     /* [HGM] by default clear Crazyhouse holdings, if present */
17700     if(gameInfo.holdingsWidth) {
17701        for(i=0; i<BOARD_HEIGHT; i++) {
17702            board[i][0]             = EmptySquare; /* black holdings */
17703            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17704            board[i][1]             = (ChessSquare) 0; /* black counts */
17705            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17706        }
17707     }
17708
17709     /* [HGM] look for Crazyhouse holdings here */
17710     while(*p==' ') p++;
17711     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17712         if(*p == '[') p++;
17713         if(*p == '-' ) p++; /* empty holdings */ else {
17714             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17715             /* if we would allow FEN reading to set board size, we would   */
17716             /* have to add holdings and shift the board read so far here   */
17717             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17718                 p++;
17719                 if((int) piece >= (int) BlackPawn ) {
17720                     i = (int)piece - (int)BlackPawn;
17721                     i = PieceToNumber((ChessSquare)i);
17722                     if( i >= gameInfo.holdingsSize ) return FALSE;
17723                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17724                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17725                 } else {
17726                     i = (int)piece - (int)WhitePawn;
17727                     i = PieceToNumber((ChessSquare)i);
17728                     if( i >= gameInfo.holdingsSize ) return FALSE;
17729                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17730                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17731                 }
17732             }
17733         }
17734         if(*p == ']') p++;
17735     }
17736
17737     while(*p == ' ') p++;
17738
17739     /* Active color */
17740     c = *p++;
17741     if(appData.colorNickNames) {
17742       if( c == appData.colorNickNames[0] ) c = 'w'; else
17743       if( c == appData.colorNickNames[1] ) c = 'b';
17744     }
17745     switch (c) {
17746       case 'w':
17747         *blackPlaysFirst = FALSE;
17748         break;
17749       case 'b':
17750         *blackPlaysFirst = TRUE;
17751         break;
17752       default:
17753         return FALSE;
17754     }
17755
17756     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17757     /* return the extra info in global variiables             */
17758
17759     /* set defaults in case FEN is incomplete */
17760     board[EP_STATUS] = EP_UNKNOWN;
17761     for(i=0; i<nrCastlingRights; i++ ) {
17762         board[CASTLING][i] =
17763             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17764     }   /* assume possible unless obviously impossible */
17765     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17766     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17767     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17768                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17769     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17770     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17771     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17772                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17773     FENrulePlies = 0;
17774
17775     while(*p==' ') p++;
17776     if(nrCastlingRights) {
17777       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17778       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17779           /* castling indicator present, so default becomes no castlings */
17780           for(i=0; i<nrCastlingRights; i++ ) {
17781                  board[CASTLING][i] = NoRights;
17782           }
17783       }
17784       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17785              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17786              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17787              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17788         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17789
17790         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17791             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17792             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17793         }
17794         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17795             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17796         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17797                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17798         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17799                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17800         switch(c) {
17801           case'K':
17802               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17803               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17804               board[CASTLING][2] = whiteKingFile;
17805               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17806               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17807               break;
17808           case'Q':
17809               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17810               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17811               board[CASTLING][2] = whiteKingFile;
17812               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17813               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17814               break;
17815           case'k':
17816               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17817               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17818               board[CASTLING][5] = blackKingFile;
17819               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17820               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17821               break;
17822           case'q':
17823               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17824               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17825               board[CASTLING][5] = blackKingFile;
17826               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17827               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17828           case '-':
17829               break;
17830           default: /* FRC castlings */
17831               if(c >= 'a') { /* black rights */
17832                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17833                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17834                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17835                   if(i == BOARD_RGHT) break;
17836                   board[CASTLING][5] = i;
17837                   c -= AAA;
17838                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17839                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17840                   if(c > i)
17841                       board[CASTLING][3] = c;
17842                   else
17843                       board[CASTLING][4] = c;
17844               } else { /* white rights */
17845                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17846                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17847                     if(board[0][i] == WhiteKing) break;
17848                   if(i == BOARD_RGHT) break;
17849                   board[CASTLING][2] = i;
17850                   c -= AAA - 'a' + 'A';
17851                   if(board[0][c] >= WhiteKing) break;
17852                   if(c > i)
17853                       board[CASTLING][0] = c;
17854                   else
17855                       board[CASTLING][1] = c;
17856               }
17857         }
17858       }
17859       for(i=0; i<nrCastlingRights; i++)
17860         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17861       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17862     if (appData.debugMode) {
17863         fprintf(debugFP, "FEN castling rights:");
17864         for(i=0; i<nrCastlingRights; i++)
17865         fprintf(debugFP, " %d", board[CASTLING][i]);
17866         fprintf(debugFP, "\n");
17867     }
17868
17869       while(*p==' ') p++;
17870     }
17871
17872     /* read e.p. field in games that know e.p. capture */
17873     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17874        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17875        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17876       if(*p=='-') {
17877         p++; board[EP_STATUS] = EP_NONE;
17878       } else {
17879          char c = *p++ - AAA;
17880
17881          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17882          if(*p >= '0' && *p <='9') p++;
17883          board[EP_STATUS] = c;
17884       }
17885     }
17886
17887
17888     if(sscanf(p, "%d", &i) == 1) {
17889         FENrulePlies = i; /* 50-move ply counter */
17890         /* (The move number is still ignored)    */
17891     }
17892
17893     return TRUE;
17894 }
17895
17896 void
17897 EditPositionPasteFEN (char *fen)
17898 {
17899   if (fen != NULL) {
17900     Board initial_position;
17901
17902     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17903       DisplayError(_("Bad FEN position in clipboard"), 0);
17904       return ;
17905     } else {
17906       int savedBlackPlaysFirst = blackPlaysFirst;
17907       EditPositionEvent();
17908       blackPlaysFirst = savedBlackPlaysFirst;
17909       CopyBoard(boards[0], initial_position);
17910       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17911       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17912       DisplayBothClocks();
17913       DrawPosition(FALSE, boards[currentMove]);
17914     }
17915   }
17916 }
17917
17918 static char cseq[12] = "\\   ";
17919
17920 Boolean
17921 set_cont_sequence (char *new_seq)
17922 {
17923     int len;
17924     Boolean ret;
17925
17926     // handle bad attempts to set the sequence
17927         if (!new_seq)
17928                 return 0; // acceptable error - no debug
17929
17930     len = strlen(new_seq);
17931     ret = (len > 0) && (len < sizeof(cseq));
17932     if (ret)
17933       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17934     else if (appData.debugMode)
17935       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17936     return ret;
17937 }
17938
17939 /*
17940     reformat a source message so words don't cross the width boundary.  internal
17941     newlines are not removed.  returns the wrapped size (no null character unless
17942     included in source message).  If dest is NULL, only calculate the size required
17943     for the dest buffer.  lp argument indicats line position upon entry, and it's
17944     passed back upon exit.
17945 */
17946 int
17947 wrap (char *dest, char *src, int count, int width, int *lp)
17948 {
17949     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17950
17951     cseq_len = strlen(cseq);
17952     old_line = line = *lp;
17953     ansi = len = clen = 0;
17954
17955     for (i=0; i < count; i++)
17956     {
17957         if (src[i] == '\033')
17958             ansi = 1;
17959
17960         // if we hit the width, back up
17961         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17962         {
17963             // store i & len in case the word is too long
17964             old_i = i, old_len = len;
17965
17966             // find the end of the last word
17967             while (i && src[i] != ' ' && src[i] != '\n')
17968             {
17969                 i--;
17970                 len--;
17971             }
17972
17973             // word too long?  restore i & len before splitting it
17974             if ((old_i-i+clen) >= width)
17975             {
17976                 i = old_i;
17977                 len = old_len;
17978             }
17979
17980             // extra space?
17981             if (i && src[i-1] == ' ')
17982                 len--;
17983
17984             if (src[i] != ' ' && src[i] != '\n')
17985             {
17986                 i--;
17987                 if (len)
17988                     len--;
17989             }
17990
17991             // now append the newline and continuation sequence
17992             if (dest)
17993                 dest[len] = '\n';
17994             len++;
17995             if (dest)
17996                 strncpy(dest+len, cseq, cseq_len);
17997             len += cseq_len;
17998             line = cseq_len;
17999             clen = cseq_len;
18000             continue;
18001         }
18002
18003         if (dest)
18004             dest[len] = src[i];
18005         len++;
18006         if (!ansi)
18007             line++;
18008         if (src[i] == '\n')
18009             line = 0;
18010         if (src[i] == 'm')
18011             ansi = 0;
18012     }
18013     if (dest && appData.debugMode)
18014     {
18015         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18016             count, width, line, len, *lp);
18017         show_bytes(debugFP, src, count);
18018         fprintf(debugFP, "\ndest: ");
18019         show_bytes(debugFP, dest, len);
18020         fprintf(debugFP, "\n");
18021     }
18022     *lp = dest ? line : old_line;
18023
18024     return len;
18025 }
18026
18027 // [HGM] vari: routines for shelving variations
18028 Boolean modeRestore = FALSE;
18029
18030 void
18031 PushInner (int firstMove, int lastMove)
18032 {
18033         int i, j, nrMoves = lastMove - firstMove;
18034
18035         // push current tail of game on stack
18036         savedResult[storedGames] = gameInfo.result;
18037         savedDetails[storedGames] = gameInfo.resultDetails;
18038         gameInfo.resultDetails = NULL;
18039         savedFirst[storedGames] = firstMove;
18040         savedLast [storedGames] = lastMove;
18041         savedFramePtr[storedGames] = framePtr;
18042         framePtr -= nrMoves; // reserve space for the boards
18043         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18044             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18045             for(j=0; j<MOVE_LEN; j++)
18046                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18047             for(j=0; j<2*MOVE_LEN; j++)
18048                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18049             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18050             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18051             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18052             pvInfoList[firstMove+i-1].depth = 0;
18053             commentList[framePtr+i] = commentList[firstMove+i];
18054             commentList[firstMove+i] = NULL;
18055         }
18056
18057         storedGames++;
18058         forwardMostMove = firstMove; // truncate game so we can start variation
18059 }
18060
18061 void
18062 PushTail (int firstMove, int lastMove)
18063 {
18064         if(appData.icsActive) { // only in local mode
18065                 forwardMostMove = currentMove; // mimic old ICS behavior
18066                 return;
18067         }
18068         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18069
18070         PushInner(firstMove, lastMove);
18071         if(storedGames == 1) GreyRevert(FALSE);
18072         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18073 }
18074
18075 void
18076 PopInner (Boolean annotate)
18077 {
18078         int i, j, nrMoves;
18079         char buf[8000], moveBuf[20];
18080
18081         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18082         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18083         nrMoves = savedLast[storedGames] - currentMove;
18084         if(annotate) {
18085                 int cnt = 10;
18086                 if(!WhiteOnMove(currentMove))
18087                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18088                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18089                 for(i=currentMove; i<forwardMostMove; i++) {
18090                         if(WhiteOnMove(i))
18091                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18092                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18093                         strcat(buf, moveBuf);
18094                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18095                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18096                 }
18097                 strcat(buf, ")");
18098         }
18099         for(i=1; i<=nrMoves; i++) { // copy last variation back
18100             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18101             for(j=0; j<MOVE_LEN; j++)
18102                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18103             for(j=0; j<2*MOVE_LEN; j++)
18104                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18105             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18106             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18107             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18108             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18109             commentList[currentMove+i] = commentList[framePtr+i];
18110             commentList[framePtr+i] = NULL;
18111         }
18112         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18113         framePtr = savedFramePtr[storedGames];
18114         gameInfo.result = savedResult[storedGames];
18115         if(gameInfo.resultDetails != NULL) {
18116             free(gameInfo.resultDetails);
18117       }
18118         gameInfo.resultDetails = savedDetails[storedGames];
18119         forwardMostMove = currentMove + nrMoves;
18120 }
18121
18122 Boolean
18123 PopTail (Boolean annotate)
18124 {
18125         if(appData.icsActive) return FALSE; // only in local mode
18126         if(!storedGames) return FALSE; // sanity
18127         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18128
18129         PopInner(annotate);
18130         if(currentMove < forwardMostMove) ForwardEvent(); else
18131         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18132
18133         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18134         return TRUE;
18135 }
18136
18137 void
18138 CleanupTail ()
18139 {       // remove all shelved variations
18140         int i;
18141         for(i=0; i<storedGames; i++) {
18142             if(savedDetails[i])
18143                 free(savedDetails[i]);
18144             savedDetails[i] = NULL;
18145         }
18146         for(i=framePtr; i<MAX_MOVES; i++) {
18147                 if(commentList[i]) free(commentList[i]);
18148                 commentList[i] = NULL;
18149         }
18150         framePtr = MAX_MOVES-1;
18151         storedGames = 0;
18152 }
18153
18154 void
18155 LoadVariation (int index, char *text)
18156 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18157         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18158         int level = 0, move;
18159
18160         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18161         // first find outermost bracketing variation
18162         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18163             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18164                 if(*p == '{') wait = '}'; else
18165                 if(*p == '[') wait = ']'; else
18166                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18167                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18168             }
18169             if(*p == wait) wait = NULLCHAR; // closing ]} found
18170             p++;
18171         }
18172         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18173         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18174         end[1] = NULLCHAR; // clip off comment beyond variation
18175         ToNrEvent(currentMove-1);
18176         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18177         // kludge: use ParsePV() to append variation to game
18178         move = currentMove;
18179         ParsePV(start, TRUE, TRUE);
18180         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18181         ClearPremoveHighlights();
18182         CommentPopDown();
18183         ToNrEvent(currentMove+1);
18184 }
18185
18186 void
18187 LoadTheme ()
18188 {
18189     char *p, *q, buf[MSG_SIZ];
18190     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18191         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18192         ParseArgsFromString(buf);
18193         ActivateTheme(TRUE); // also redo colors
18194         return;
18195     }
18196     p = nickName;
18197     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18198     {
18199         int len;
18200         q = appData.themeNames;
18201         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18202       if(appData.useBitmaps) {
18203         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18204                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18205                 appData.liteBackTextureMode,
18206                 appData.darkBackTextureMode );
18207       } else {
18208         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18209                 Col2Text(2),   // lightSquareColor
18210                 Col2Text(3) ); // darkSquareColor
18211       }
18212       if(appData.useBorder) {
18213         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18214                 appData.border);
18215       } else {
18216         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18217       }
18218       if(appData.useFont) {
18219         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18220                 appData.renderPiecesWithFont,
18221                 appData.fontToPieceTable,
18222                 Col2Text(9),    // appData.fontBackColorWhite
18223                 Col2Text(10) ); // appData.fontForeColorBlack
18224       } else {
18225         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18226                 appData.pieceDirectory);
18227         if(!appData.pieceDirectory[0])
18228           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18229                 Col2Text(0),   // whitePieceColor
18230                 Col2Text(1) ); // blackPieceColor
18231       }
18232       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18233                 Col2Text(4),   // highlightSquareColor
18234                 Col2Text(5) ); // premoveHighlightColor
18235         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18236         if(insert != q) insert[-1] = NULLCHAR;
18237         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18238         if(q)   free(q);
18239     }
18240     ActivateTheme(FALSE);
18241 }