Fix exposing of premove highlight and move exclusion XB
[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, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1000         return;
1001     }
1002     p = engineName;
1003     while(q = strchr(p, SLASH)) p = q+1;
1004     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005     if(engineDir[0] != NULLCHAR) {
1006         ASSIGN(appData.directory[i], engineDir); p = engineName;
1007     } else if(p != engineName) { // derive directory from engine path, when not given
1008         p[-1] = 0;
1009         ASSIGN(appData.directory[i], engineName);
1010         p[-1] = SLASH;
1011         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012     } else { ASSIGN(appData.directory[i], "."); }
1013     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014     if(params[0]) {
1015         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016         snprintf(command, MSG_SIZ, "%s %s", p, params);
1017         p = command;
1018     }
1019     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020     ASSIGN(appData.chessProgram[i], p);
1021     appData.isUCI[i] = isUCI;
1022     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023     appData.hasOwnBookUCI[i] = hasBook;
1024     if(!nickName[0]) useNick = FALSE;
1025     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1026     if(addToList) {
1027         int len;
1028         char quote;
1029         q = firstChessProgramNames;
1030         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033                         quote, p, quote, appData.directory[i],
1034                         useNick ? " -fn \"" : "",
1035                         useNick ? nickName : "",
1036                         useNick ? "\"" : "",
1037                         v1 ? " -firstProtocolVersion 1" : "",
1038                         hasBook ? "" : " -fNoOwnBookUCI",
1039                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040                         storeVariant ? " -variant " : "",
1041                         storeVariant ? VariantName(gameInfo.variant) : "");
1042         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044         if(insert != q) insert[-1] = NULLCHAR;
1045         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046         if(q)   free(q);
1047         FloatToFront(&appData.recentEngineList, buf);
1048     }
1049     ReplaceEngine(cps, i);
1050 }
1051
1052 void
1053 InitTimeControls ()
1054 {
1055     int matched, min, sec;
1056     /*
1057      * Parse timeControl resource
1058      */
1059     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060                           appData.movesPerSession)) {
1061         char buf[MSG_SIZ];
1062         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063         DisplayFatalError(buf, 0, 2);
1064     }
1065
1066     /*
1067      * Parse searchTime resource
1068      */
1069     if (*appData.searchTime != NULLCHAR) {
1070         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071         if (matched == 1) {
1072             searchTime = min * 60;
1073         } else if (matched == 2) {
1074             searchTime = min * 60 + sec;
1075         } else {
1076             char buf[MSG_SIZ];
1077             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078             DisplayFatalError(buf, 0, 2);
1079         }
1080     }
1081 }
1082
1083 void
1084 InitBackEnd1 ()
1085 {
1086
1087     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089
1090     GetTimeMark(&programStartTime);
1091     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092     appData.seedBase = random() + (random()<<15);
1093     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094
1095     ClearProgramStats();
1096     programStats.ok_to_send = 1;
1097     programStats.seen_stat = 0;
1098
1099     /*
1100      * Initialize game list
1101      */
1102     ListNew(&gameList);
1103
1104
1105     /*
1106      * Internet chess server status
1107      */
1108     if (appData.icsActive) {
1109         appData.matchMode = FALSE;
1110         appData.matchGames = 0;
1111 #if ZIPPY
1112         appData.noChessProgram = !appData.zippyPlay;
1113 #else
1114         appData.zippyPlay = FALSE;
1115         appData.zippyTalk = FALSE;
1116         appData.noChessProgram = TRUE;
1117 #endif
1118         if (*appData.icsHelper != NULLCHAR) {
1119             appData.useTelnet = TRUE;
1120             appData.telnetProgram = appData.icsHelper;
1121         }
1122     } else {
1123         appData.zippyTalk = appData.zippyPlay = FALSE;
1124     }
1125
1126     /* [AS] Initialize pv info list [HGM] and game state */
1127     {
1128         int i, j;
1129
1130         for( i=0; i<=framePtr; i++ ) {
1131             pvInfoList[i].depth = -1;
1132             boards[i][EP_STATUS] = EP_NONE;
1133             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1134         }
1135     }
1136
1137     InitTimeControls();
1138
1139     /* [AS] Adjudication threshold */
1140     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141
1142     InitEngine(&first, 0);
1143     InitEngine(&second, 1);
1144     CommonEngineInit();
1145
1146     pairing.which = "pairing"; // pairing engine
1147     pairing.pr = NoProc;
1148     pairing.isr = NULL;
1149     pairing.program = appData.pairingEngine;
1150     pairing.host = "localhost";
1151     pairing.dir = ".";
1152
1153     if (appData.icsActive) {
1154         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1155     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156         appData.clockMode = FALSE;
1157         first.sendTime = second.sendTime = 0;
1158     }
1159
1160 #if ZIPPY
1161     /* Override some settings from environment variables, for backward
1162        compatibility.  Unfortunately it's not feasible to have the env
1163        vars just set defaults, at least in xboard.  Ugh.
1164     */
1165     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1166       ZippyInit();
1167     }
1168 #endif
1169
1170     if (!appData.icsActive) {
1171       char buf[MSG_SIZ];
1172       int len;
1173
1174       /* Check for variants that are supported only in ICS mode,
1175          or not at all.  Some that are accepted here nevertheless
1176          have bugs; see comments below.
1177       */
1178       VariantClass variant = StringToVariant(appData.variant);
1179       switch (variant) {
1180       case VariantBughouse:     /* need four players and two boards */
1181       case VariantKriegspiel:   /* need to hide pieces and move details */
1182         /* case VariantFischeRandom: (Fabien: moved below) */
1183         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184         if( (len >= MSG_SIZ) && appData.debugMode )
1185           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186
1187         DisplayFatalError(buf, 0, 2);
1188         return;
1189
1190       case VariantUnknown:
1191       case VariantLoadable:
1192       case Variant29:
1193       case Variant30:
1194       case Variant31:
1195       case Variant32:
1196       case Variant33:
1197       case Variant34:
1198       case Variant35:
1199       case Variant36:
1200       default:
1201         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202         if( (len >= MSG_SIZ) && appData.debugMode )
1203           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204
1205         DisplayFatalError(buf, 0, 2);
1206         return;
1207
1208       case VariantNormal:     /* definitely works! */
1209         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211           return;
1212         }
1213       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1214       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1215       case VariantGothic:     /* [HGM] should work */
1216       case VariantCapablanca: /* [HGM] should work */
1217       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1218       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1219       case VariantChu:        /* [HGM] experimental */
1220       case VariantKnightmate: /* [HGM] should work */
1221       case VariantCylinder:   /* [HGM] untested */
1222       case VariantFalcon:     /* [HGM] untested */
1223       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224                                  offboard interposition not understood */
1225       case VariantWildCastle: /* pieces not automatically shuffled */
1226       case VariantNoCastle:   /* pieces not automatically shuffled */
1227       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228       case VariantLosers:     /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantSuicide:    /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantGiveaway:   /* should work except for win condition,
1233                                  and doesn't know captures are mandatory */
1234       case VariantTwoKings:   /* should work */
1235       case VariantAtomic:     /* should work except for win condition */
1236       case Variant3Check:     /* should work except for win condition */
1237       case VariantShatranj:   /* should work except for all win conditions */
1238       case VariantMakruk:     /* should work except for draw countdown */
1239       case VariantASEAN :     /* should work except for draw countdown */
1240       case VariantBerolina:   /* might work if TestLegality is off */
1241       case VariantCapaRandom: /* should work */
1242       case VariantJanus:      /* should work */
1243       case VariantSuper:      /* experimental */
1244       case VariantGreat:      /* experimental, requires legality testing to be off */
1245       case VariantSChess:     /* S-Chess, should work */
1246       case VariantGrand:      /* should work */
1247       case VariantSpartan:    /* should work */
1248       case VariantLion:       /* should work */
1249       case VariantChuChess:   /* should work */
1250         break;
1251       }
1252     }
1253
1254 }
1255
1256 int
1257 NextIntegerFromString (char ** str, long * value)
1258 {
1259     int result = -1;
1260     char * s = *str;
1261
1262     while( *s == ' ' || *s == '\t' ) {
1263         s++;
1264     }
1265
1266     *value = 0;
1267
1268     if( *s >= '0' && *s <= '9' ) {
1269         while( *s >= '0' && *s <= '9' ) {
1270             *value = *value * 10 + (*s - '0');
1271             s++;
1272         }
1273
1274         result = 0;
1275     }
1276
1277     *str = s;
1278
1279     return result;
1280 }
1281
1282 int
1283 NextTimeControlFromString (char ** str, long * value)
1284 {
1285     long temp;
1286     int result = NextIntegerFromString( str, &temp );
1287
1288     if( result == 0 ) {
1289         *value = temp * 60; /* Minutes */
1290         if( **str == ':' ) {
1291             (*str)++;
1292             result = NextIntegerFromString( str, &temp );
1293             *value += temp; /* Seconds */
1294         }
1295     }
1296
1297     return result;
1298 }
1299
1300 int
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303     int result = -1, type = 0; long temp, temp2;
1304
1305     if(**str != ':') return -1; // old params remain in force!
1306     (*str)++;
1307     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308     if( NextIntegerFromString( str, &temp ) ) return -1;
1309     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310
1311     if(**str != '/') {
1312         /* time only: incremental or sudden-death time control */
1313         if(**str == '+') { /* increment follows; read it */
1314             (*str)++;
1315             if(**str == '!') type = *(*str)++; // Bronstein TC
1316             if(result = NextIntegerFromString( str, &temp2)) return -1;
1317             *inc = temp2 * 1000;
1318             if(**str == '.') { // read fraction of increment
1319                 char *start = ++(*str);
1320                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321                 temp2 *= 1000;
1322                 while(start++ < *str) temp2 /= 10;
1323                 *inc += temp2;
1324             }
1325         } else *inc = 0;
1326         *moves = 0; *tc = temp * 1000; *incType = type;
1327         return 0;
1328     }
1329
1330     (*str)++; /* classical time control */
1331     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1332
1333     if(result == 0) {
1334         *moves = temp;
1335         *tc    = temp2 * 1000;
1336         *inc   = 0;
1337         *incType = type;
1338     }
1339     return result;
1340 }
1341
1342 int
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 {   /* [HGM] get time to add from the multi-session time-control string */
1345     int incType, moves=1; /* kludge to force reading of first session */
1346     long time, increment;
1347     char *s = tcString;
1348
1349     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350     do {
1351         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353         if(movenr == -1) return time;    /* last move before new session     */
1354         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356         if(!moves) return increment;     /* current session is incremental   */
1357         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358     } while(movenr >= -1);               /* try again for next session       */
1359
1360     return 0; // no new time quota on this move
1361 }
1362
1363 int
1364 ParseTimeControl (char *tc, float ti, int mps)
1365 {
1366   long tc1;
1367   long tc2;
1368   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369   int min, sec=0;
1370
1371   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1374   if(ti > 0) {
1375
1376     if(mps)
1377       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378     else
1379       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380   } else {
1381     if(mps)
1382       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383     else
1384       snprintf(buf, MSG_SIZ, ":%s", mytc);
1385   }
1386   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387
1388   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1389     return FALSE;
1390   }
1391
1392   if( *tc == '/' ) {
1393     /* Parse second time control */
1394     tc++;
1395
1396     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1397       return FALSE;
1398     }
1399
1400     if( tc2 == 0 ) {
1401       return FALSE;
1402     }
1403
1404     timeControl_2 = tc2 * 1000;
1405   }
1406   else {
1407     timeControl_2 = 0;
1408   }
1409
1410   if( tc1 == 0 ) {
1411     return FALSE;
1412   }
1413
1414   timeControl = tc1 * 1000;
1415
1416   if (ti >= 0) {
1417     timeIncrement = ti * 1000;  /* convert to ms */
1418     movesPerSession = 0;
1419   } else {
1420     timeIncrement = 0;
1421     movesPerSession = mps;
1422   }
1423   return TRUE;
1424 }
1425
1426 void
1427 InitBackEnd2 ()
1428 {
1429     if (appData.debugMode) {
1430 #    ifdef __GIT_VERSION
1431       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 #    else
1433       fprintf(debugFP, "Version: %s\n", programVersion);
1434 #    endif
1435     }
1436     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437
1438     set_cont_sequence(appData.wrapContSeq);
1439     if (appData.matchGames > 0) {
1440         appData.matchMode = TRUE;
1441     } else if (appData.matchMode) {
1442         appData.matchGames = 1;
1443     }
1444     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445         appData.matchGames = appData.sameColorGames;
1446     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449     }
1450     Reset(TRUE, FALSE);
1451     if (appData.noChessProgram || first.protocolVersion == 1) {
1452       InitBackEnd3();
1453     } else {
1454       /* kludge: allow timeout for initial "feature" commands */
1455       FreezeUI();
1456       DisplayMessage("", _("Starting chess program"));
1457       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1458     }
1459 }
1460
1461 int
1462 CalculateIndex (int index, int gameNr)
1463 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464     int res;
1465     if(index > 0) return index; // fixed nmber
1466     if(index == 0) return 1;
1467     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1469     return res;
1470 }
1471
1472 int
1473 LoadGameOrPosition (int gameNr)
1474 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475     if (*appData.loadGameFile != NULLCHAR) {
1476         if (!LoadGameFromFile(appData.loadGameFile,
1477                 CalculateIndex(appData.loadGameIndex, gameNr),
1478                               appData.loadGameFile, FALSE)) {
1479             DisplayFatalError(_("Bad game file"), 0, 1);
1480             return 0;
1481         }
1482     } else if (*appData.loadPositionFile != NULLCHAR) {
1483         if (!LoadPositionFromFile(appData.loadPositionFile,
1484                 CalculateIndex(appData.loadPositionIndex, gameNr),
1485                                   appData.loadPositionFile)) {
1486             DisplayFatalError(_("Bad position file"), 0, 1);
1487             return 0;
1488         }
1489     }
1490     return 1;
1491 }
1492
1493 void
1494 ReserveGame (int gameNr, char resChar)
1495 {
1496     FILE *tf = fopen(appData.tourneyFile, "r+");
1497     char *p, *q, c, buf[MSG_SIZ];
1498     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499     safeStrCpy(buf, lastMsg, MSG_SIZ);
1500     DisplayMessage(_("Pick new game"), "");
1501     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502     ParseArgsFromFile(tf);
1503     p = q = appData.results;
1504     if(appData.debugMode) {
1505       char *r = appData.participants;
1506       fprintf(debugFP, "results = '%s'\n", p);
1507       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508       fprintf(debugFP, "\n");
1509     }
1510     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511     nextGame = q - p;
1512     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513     safeStrCpy(q, p, strlen(p) + 2);
1514     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518         q[nextGame] = '*';
1519     }
1520     fseek(tf, -(strlen(p)+4), SEEK_END);
1521     c = fgetc(tf);
1522     if(c != '"') // depending on DOS or Unix line endings we can be one off
1523          fseek(tf, -(strlen(p)+2), SEEK_END);
1524     else fseek(tf, -(strlen(p)+3), SEEK_END);
1525     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526     DisplayMessage(buf, "");
1527     free(p); appData.results = q;
1528     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530       int round = appData.defaultMatchGames * appData.tourneyType;
1531       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1532          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533         UnloadEngine(&first);  // next game belongs to other pairing;
1534         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535     }
1536     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1537 }
1538
1539 void
1540 MatchEvent (int mode)
1541 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542         int dummy;
1543         if(matchMode) { // already in match mode: switch it off
1544             abortMatch = TRUE;
1545             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546             return;
1547         }
1548 //      if(gameMode != BeginningOfGame) {
1549 //          DisplayError(_("You can only start a match from the initial position."), 0);
1550 //          return;
1551 //      }
1552         abortMatch = FALSE;
1553         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554         /* Set up machine vs. machine match */
1555         nextGame = 0;
1556         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557         if(appData.tourneyFile[0]) {
1558             ReserveGame(-1, 0);
1559             if(nextGame > appData.matchGames) {
1560                 char buf[MSG_SIZ];
1561                 if(strchr(appData.results, '*') == NULL) {
1562                     FILE *f;
1563                     appData.tourneyCycles++;
1564                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565                         fclose(f);
1566                         NextTourneyGame(-1, &dummy);
1567                         ReserveGame(-1, 0);
1568                         if(nextGame <= appData.matchGames) {
1569                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570                             matchMode = mode;
1571                             ScheduleDelayedEvent(NextMatchGame, 10000);
1572                             return;
1573                         }
1574                     }
1575                 }
1576                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577                 DisplayError(buf, 0);
1578                 appData.tourneyFile[0] = 0;
1579                 return;
1580             }
1581         } else
1582         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1583             DisplayFatalError(_("Can't have a match with no chess programs"),
1584                               0, 2);
1585             return;
1586         }
1587         matchMode = mode;
1588         matchGame = roundNr = 1;
1589         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1590         NextMatchGame();
1591 }
1592
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594
1595 void
1596 InitBackEnd3 P((void))
1597 {
1598     GameMode initialMode;
1599     char buf[MSG_SIZ];
1600     int err, len;
1601
1602     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1603        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1604         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1605        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1606        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607         char c, *q = first.variants, *p = strchr(q, ',');
1608         if(p) *p = NULLCHAR;
1609         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610             int w, h, s;
1611             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614             Reset(TRUE, FALSE);         // and re-initialize
1615         }
1616         if(p) *p = ',';
1617     }
1618
1619     InitChessProgram(&first, startedFromSetupPosition);
1620
1621     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1622         free(programVersion);
1623         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626     }
1627
1628     if (appData.icsActive) {
1629 #ifdef WIN32
1630         /* [DM] Make a console window if needed [HGM] merged ifs */
1631         ConsoleCreate();
1632 #endif
1633         err = establish();
1634         if (err != 0)
1635           {
1636             if (*appData.icsCommPort != NULLCHAR)
1637               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638                              appData.icsCommPort);
1639             else
1640               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641                         appData.icsHost, appData.icsPort);
1642
1643             if( (len >= MSG_SIZ) && appData.debugMode )
1644               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645
1646             DisplayFatalError(buf, err, 1);
1647             return;
1648         }
1649         SetICSMode();
1650         telnetISR =
1651           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652         fromUserISR =
1653           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656     } else if (appData.noChessProgram) {
1657         SetNCPMode();
1658     } else {
1659         SetGNUMode();
1660     }
1661
1662     if (*appData.cmailGameName != NULLCHAR) {
1663         SetCmailMode();
1664         OpenLoopback(&cmailPR);
1665         cmailISR =
1666           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1667     }
1668
1669     ThawUI();
1670     DisplayMessage("", "");
1671     if (StrCaseCmp(appData.initialMode, "") == 0) {
1672       initialMode = BeginningOfGame;
1673       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677         ModeHighlight();
1678       }
1679     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680       initialMode = TwoMachinesPlay;
1681     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682       initialMode = AnalyzeFile;
1683     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684       initialMode = AnalyzeMode;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686       initialMode = MachinePlaysWhite;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688       initialMode = MachinePlaysBlack;
1689     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690       initialMode = EditGame;
1691     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692       initialMode = EditPosition;
1693     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694       initialMode = Training;
1695     } else {
1696       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697       if( (len >= MSG_SIZ) && appData.debugMode )
1698         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699
1700       DisplayFatalError(buf, 0, 2);
1701       return;
1702     }
1703
1704     if (appData.matchMode) {
1705         if(appData.tourneyFile[0]) { // start tourney from command line
1706             FILE *f;
1707             if(f = fopen(appData.tourneyFile, "r")) {
1708                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709                 fclose(f);
1710                 appData.clockMode = TRUE;
1711                 SetGNUMode();
1712             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713         }
1714         MatchEvent(TRUE);
1715     } else if (*appData.cmailGameName != NULLCHAR) {
1716         /* Set up cmail mode */
1717         ReloadCmailMsgEvent(TRUE);
1718     } else {
1719         /* Set up other modes */
1720         if (initialMode == AnalyzeFile) {
1721           if (*appData.loadGameFile == NULLCHAR) {
1722             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1723             return;
1724           }
1725         }
1726         if (*appData.loadGameFile != NULLCHAR) {
1727             (void) LoadGameFromFile(appData.loadGameFile,
1728                                     appData.loadGameIndex,
1729                                     appData.loadGameFile, TRUE);
1730         } else if (*appData.loadPositionFile != NULLCHAR) {
1731             (void) LoadPositionFromFile(appData.loadPositionFile,
1732                                         appData.loadPositionIndex,
1733                                         appData.loadPositionFile);
1734             /* [HGM] try to make self-starting even after FEN load */
1735             /* to allow automatic setup of fairy variants with wtm */
1736             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737                 gameMode = BeginningOfGame;
1738                 setboardSpoiledMachineBlack = 1;
1739             }
1740             /* [HGM] loadPos: make that every new game uses the setup */
1741             /* from file as long as we do not switch variant          */
1742             if(!blackPlaysFirst) {
1743                 startedFromPositionFile = TRUE;
1744                 CopyBoard(filePosition, boards[0]);
1745                 CopyBoard(initialPosition, boards[0]);
1746             }
1747         } else if(*appData.fen != NULLCHAR) {
1748             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749                 startedFromPositionFile = TRUE;
1750                 Reset(TRUE, TRUE);
1751             }
1752         }
1753         if (initialMode == AnalyzeMode) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1756             return;
1757           }
1758           if (appData.icsActive) {
1759             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1760             return;
1761           }
1762           AnalyzeModeEvent();
1763         } else if (initialMode == AnalyzeFile) {
1764           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765           ShowThinkingEvent();
1766           AnalyzeFileEvent();
1767           AnalysisPeriodicEvent(1);
1768         } else if (initialMode == MachinePlaysWhite) {
1769           if (appData.noChessProgram) {
1770             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1771                               0, 2);
1772             return;
1773           }
1774           if (appData.icsActive) {
1775             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1776                               0, 2);
1777             return;
1778           }
1779           MachineWhiteEvent();
1780         } else if (initialMode == MachinePlaysBlack) {
1781           if (appData.noChessProgram) {
1782             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1783                               0, 2);
1784             return;
1785           }
1786           if (appData.icsActive) {
1787             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1788                               0, 2);
1789             return;
1790           }
1791           MachineBlackEvent();
1792         } else if (initialMode == TwoMachinesPlay) {
1793           if (appData.noChessProgram) {
1794             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1795                               0, 2);
1796             return;
1797           }
1798           if (appData.icsActive) {
1799             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1800                               0, 2);
1801             return;
1802           }
1803           TwoMachinesEvent();
1804         } else if (initialMode == EditGame) {
1805           EditGameEvent();
1806         } else if (initialMode == EditPosition) {
1807           EditPositionEvent();
1808         } else if (initialMode == Training) {
1809           if (*appData.loadGameFile == NULLCHAR) {
1810             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811             return;
1812           }
1813           TrainingEvent();
1814         }
1815     }
1816 }
1817
1818 void
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 {
1821     DisplayBook(current+1);
1822
1823     MoveHistorySet( movelist, first, last, current, pvInfoList );
1824
1825     EvalGraphSet( first, last, current, pvInfoList );
1826
1827     MakeEngineOutputTitle();
1828 }
1829
1830 /*
1831  * Establish will establish a contact to a remote host.port.
1832  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833  *  used to talk to the host.
1834  * Returns 0 if okay, error code if not.
1835  */
1836 int
1837 establish ()
1838 {
1839     char buf[MSG_SIZ];
1840
1841     if (*appData.icsCommPort != NULLCHAR) {
1842         /* Talk to the host through a serial comm port */
1843         return OpenCommPort(appData.icsCommPort, &icsPR);
1844
1845     } else if (*appData.gateway != NULLCHAR) {
1846         if (*appData.remoteShell == NULLCHAR) {
1847             /* Use the rcmd protocol to run telnet program on a gateway host */
1848             snprintf(buf, sizeof(buf), "%s %s %s",
1849                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1850             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1851
1852         } else {
1853             /* Use the rsh program to run telnet program on a gateway host */
1854             if (*appData.remoteUser == NULLCHAR) {
1855                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856                         appData.gateway, appData.telnetProgram,
1857                         appData.icsHost, appData.icsPort);
1858             } else {
1859                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860                         appData.remoteShell, appData.gateway,
1861                         appData.remoteUser, appData.telnetProgram,
1862                         appData.icsHost, appData.icsPort);
1863             }
1864             return StartChildProcess(buf, "", &icsPR);
1865
1866         }
1867     } else if (appData.useTelnet) {
1868         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1869
1870     } else {
1871         /* TCP socket interface differs somewhat between
1872            Unix and NT; handle details in the front end.
1873            */
1874         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1875     }
1876 }
1877
1878 void
1879 EscapeExpand (char *p, char *q)
1880 {       // [HGM] initstring: routine to shape up string arguments
1881         while(*p++ = *q++) if(p[-1] == '\\')
1882             switch(*q++) {
1883                 case 'n': p[-1] = '\n'; break;
1884                 case 'r': p[-1] = '\r'; break;
1885                 case 't': p[-1] = '\t'; break;
1886                 case '\\': p[-1] = '\\'; break;
1887                 case 0: *p = 0; return;
1888                 default: p[-1] = q[-1]; break;
1889             }
1890 }
1891
1892 void
1893 show_bytes (FILE *fp, char *buf, int count)
1894 {
1895     while (count--) {
1896         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897             fprintf(fp, "\\%03o", *buf & 0xff);
1898         } else {
1899             putc(*buf, fp);
1900         }
1901         buf++;
1902     }
1903     fflush(fp);
1904 }
1905
1906 /* Returns an errno value */
1907 int
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 {
1910     char buf[8192], *p, *q, *buflim;
1911     int left, newcount, outcount;
1912
1913     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914         *appData.gateway != NULLCHAR) {
1915         if (appData.debugMode) {
1916             fprintf(debugFP, ">ICS: ");
1917             show_bytes(debugFP, message, count);
1918             fprintf(debugFP, "\n");
1919         }
1920         return OutputToProcess(pr, message, count, outError);
1921     }
1922
1923     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1924     p = message;
1925     q = buf;
1926     left = count;
1927     newcount = 0;
1928     while (left) {
1929         if (q >= buflim) {
1930             if (appData.debugMode) {
1931                 fprintf(debugFP, ">ICS: ");
1932                 show_bytes(debugFP, buf, newcount);
1933                 fprintf(debugFP, "\n");
1934             }
1935             outcount = OutputToProcess(pr, buf, newcount, outError);
1936             if (outcount < newcount) return -1; /* to be sure */
1937             q = buf;
1938             newcount = 0;
1939         }
1940         if (*p == '\n') {
1941             *q++ = '\r';
1942             newcount++;
1943         } else if (((unsigned char) *p) == TN_IAC) {
1944             *q++ = (char) TN_IAC;
1945             newcount ++;
1946         }
1947         *q++ = *p++;
1948         newcount++;
1949         left--;
1950     }
1951     if (appData.debugMode) {
1952         fprintf(debugFP, ">ICS: ");
1953         show_bytes(debugFP, buf, newcount);
1954         fprintf(debugFP, "\n");
1955     }
1956     outcount = OutputToProcess(pr, buf, newcount, outError);
1957     if (outcount < newcount) return -1; /* to be sure */
1958     return count;
1959 }
1960
1961 void
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 {
1964     int outError, outCount;
1965     static int gotEof = 0;
1966     static FILE *ini;
1967
1968     /* Pass data read from player on to ICS */
1969     if (count > 0) {
1970         gotEof = 0;
1971         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972         if (outCount < count) {
1973             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974         }
1975         if(have_sent_ICS_logon == 2) {
1976           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977             fprintf(ini, "%s", message);
1978             have_sent_ICS_logon = 3;
1979           } else
1980             have_sent_ICS_logon = 1;
1981         } else if(have_sent_ICS_logon == 3) {
1982             fprintf(ini, "%s", message);
1983             fclose(ini);
1984           have_sent_ICS_logon = 1;
1985         }
1986     } else if (count < 0) {
1987         RemoveInputSource(isr);
1988         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989     } else if (gotEof++ > 0) {
1990         RemoveInputSource(isr);
1991         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1992     }
1993 }
1994
1995 void
1996 KeepAlive ()
1997 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000     SendToICS("date\n");
2001     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2002 }
2003
2004 /* added routine for printf style output to ics */
2005 void
2006 ics_printf (char *format, ...)
2007 {
2008     char buffer[MSG_SIZ];
2009     va_list args;
2010
2011     va_start(args, format);
2012     vsnprintf(buffer, sizeof(buffer), format, args);
2013     buffer[sizeof(buffer)-1] = '\0';
2014     SendToICS(buffer);
2015     va_end(args);
2016 }
2017
2018 void
2019 SendToICS (char *s)
2020 {
2021     int count, outCount, outError;
2022
2023     if (icsPR == NoProc) return;
2024
2025     count = strlen(s);
2026     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027     if (outCount < count) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032 /* This is used for sending logon scripts to the ICS. Sending
2033    without a delay causes problems when using timestamp on ICC
2034    (at least on my machine). */
2035 void
2036 SendToICSDelayed (char *s, long msdelay)
2037 {
2038     int count, outCount, outError;
2039
2040     if (icsPR == NoProc) return;
2041
2042     count = strlen(s);
2043     if (appData.debugMode) {
2044         fprintf(debugFP, ">ICS: ");
2045         show_bytes(debugFP, s, count);
2046         fprintf(debugFP, "\n");
2047     }
2048     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049                                       msdelay);
2050     if (outCount < count) {
2051         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2052     }
2053 }
2054
2055
2056 /* Remove all highlighting escape sequences in s
2057    Also deletes any suffix starting with '('
2058    */
2059 char *
2060 StripHighlightAndTitle (char *s)
2061 {
2062     static char retbuf[MSG_SIZ];
2063     char *p = retbuf;
2064
2065     while (*s != NULLCHAR) {
2066         while (*s == '\033') {
2067             while (*s != NULLCHAR && !isalpha(*s)) s++;
2068             if (*s != NULLCHAR) s++;
2069         }
2070         while (*s != NULLCHAR && *s != '\033') {
2071             if (*s == '(' || *s == '[') {
2072                 *p = NULLCHAR;
2073                 return retbuf;
2074             }
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 /* Remove all highlighting escape sequences in s */
2083 char *
2084 StripHighlight (char *s)
2085 {
2086     static char retbuf[MSG_SIZ];
2087     char *p = retbuf;
2088
2089     while (*s != NULLCHAR) {
2090         while (*s == '\033') {
2091             while (*s != NULLCHAR && !isalpha(*s)) s++;
2092             if (*s != NULLCHAR) s++;
2093         }
2094         while (*s != NULLCHAR && *s != '\033') {
2095             *p++ = *s++;
2096         }
2097     }
2098     *p = NULLCHAR;
2099     return retbuf;
2100 }
2101
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2104 char *
2105 VariantName (VariantClass v)
2106 {
2107     if(v == VariantUnknown || *engineVariant) return engineVariant;
2108     return variantNames[v];
2109 }
2110
2111
2112 /* Identify a variant from the strings the chess servers use or the
2113    PGN Variant tag names we use. */
2114 VariantClass
2115 StringToVariant (char *e)
2116 {
2117     char *p;
2118     int wnum = -1;
2119     VariantClass v = VariantNormal;
2120     int i, found = FALSE;
2121     char buf[MSG_SIZ], c;
2122     int len;
2123
2124     if (!e) return v;
2125
2126     /* [HGM] skip over optional board-size prefixes */
2127     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129         while( *e++ != '_');
2130     }
2131
2132     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2133         v = VariantNormal;
2134         found = TRUE;
2135     } else
2136     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137       if (p = StrCaseStr(e, variantNames[i])) {
2138         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139         v = (VariantClass) i;
2140         found = TRUE;
2141         break;
2142       }
2143     }
2144
2145     if (!found) {
2146       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147           || StrCaseStr(e, "wild/fr")
2148           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149         v = VariantFischeRandom;
2150       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151                  (i = 1, p = StrCaseStr(e, "w"))) {
2152         p += i;
2153         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2154         if (isdigit(*p)) {
2155           wnum = atoi(p);
2156         } else {
2157           wnum = -1;
2158         }
2159         switch (wnum) {
2160         case 0: /* FICS only, actually */
2161         case 1:
2162           /* Castling legal even if K starts on d-file */
2163           v = VariantWildCastle;
2164           break;
2165         case 2:
2166         case 3:
2167         case 4:
2168           /* Castling illegal even if K & R happen to start in
2169              normal positions. */
2170           v = VariantNoCastle;
2171           break;
2172         case 5:
2173         case 7:
2174         case 8:
2175         case 10:
2176         case 11:
2177         case 12:
2178         case 13:
2179         case 14:
2180         case 15:
2181         case 18:
2182         case 19:
2183           /* Castling legal iff K & R start in normal positions */
2184           v = VariantNormal;
2185           break;
2186         case 6:
2187         case 20:
2188         case 21:
2189           /* Special wilds for position setup; unclear what to do here */
2190           v = VariantLoadable;
2191           break;
2192         case 9:
2193           /* Bizarre ICC game */
2194           v = VariantTwoKings;
2195           break;
2196         case 16:
2197           v = VariantKriegspiel;
2198           break;
2199         case 17:
2200           v = VariantLosers;
2201           break;
2202         case 22:
2203           v = VariantFischeRandom;
2204           break;
2205         case 23:
2206           v = VariantCrazyhouse;
2207           break;
2208         case 24:
2209           v = VariantBughouse;
2210           break;
2211         case 25:
2212           v = Variant3Check;
2213           break;
2214         case 26:
2215           /* Not quite the same as FICS suicide! */
2216           v = VariantGiveaway;
2217           break;
2218         case 27:
2219           v = VariantAtomic;
2220           break;
2221         case 28:
2222           v = VariantShatranj;
2223           break;
2224
2225         /* Temporary names for future ICC types.  The name *will* change in
2226            the next xboard/WinBoard release after ICC defines it. */
2227         case 29:
2228           v = Variant29;
2229           break;
2230         case 30:
2231           v = Variant30;
2232           break;
2233         case 31:
2234           v = Variant31;
2235           break;
2236         case 32:
2237           v = Variant32;
2238           break;
2239         case 33:
2240           v = Variant33;
2241           break;
2242         case 34:
2243           v = Variant34;
2244           break;
2245         case 35:
2246           v = Variant35;
2247           break;
2248         case 36:
2249           v = Variant36;
2250           break;
2251         case 37:
2252           v = VariantShogi;
2253           break;
2254         case 38:
2255           v = VariantXiangqi;
2256           break;
2257         case 39:
2258           v = VariantCourier;
2259           break;
2260         case 40:
2261           v = VariantGothic;
2262           break;
2263         case 41:
2264           v = VariantCapablanca;
2265           break;
2266         case 42:
2267           v = VariantKnightmate;
2268           break;
2269         case 43:
2270           v = VariantFairy;
2271           break;
2272         case 44:
2273           v = VariantCylinder;
2274           break;
2275         case 45:
2276           v = VariantFalcon;
2277           break;
2278         case 46:
2279           v = VariantCapaRandom;
2280           break;
2281         case 47:
2282           v = VariantBerolina;
2283           break;
2284         case 48:
2285           v = VariantJanus;
2286           break;
2287         case 49:
2288           v = VariantSuper;
2289           break;
2290         case 50:
2291           v = VariantGreat;
2292           break;
2293         case -1:
2294           /* Found "wild" or "w" in the string but no number;
2295              must assume it's normal chess. */
2296           v = VariantNormal;
2297           break;
2298         default:
2299           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300           if( (len >= MSG_SIZ) && appData.debugMode )
2301             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302
2303           DisplayError(buf, 0);
2304           v = VariantUnknown;
2305           break;
2306         }
2307       }
2308     }
2309     if (appData.debugMode) {
2310       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311               e, wnum, VariantName(v));
2312     }
2313     return v;
2314 }
2315
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2318
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320    advance *index beyond it, and set leftover_start to the new value of
2321    *index; else return FALSE.  If pattern contains the character '*', it
2322    matches any sequence of characters not containing '\r', '\n', or the
2323    character following the '*' (if any), and the matched sequence(s) are
2324    copied into star_match.
2325    */
2326 int
2327 looking_at ( char *buf, int *index, char *pattern)
2328 {
2329     char *bufp = &buf[*index], *patternp = pattern;
2330     int star_count = 0;
2331     char *matchp = star_match[0];
2332
2333     for (;;) {
2334         if (*patternp == NULLCHAR) {
2335             *index = leftover_start = bufp - buf;
2336             *matchp = NULLCHAR;
2337             return TRUE;
2338         }
2339         if (*bufp == NULLCHAR) return FALSE;
2340         if (*patternp == '*') {
2341             if (*bufp == *(patternp + 1)) {
2342                 *matchp = NULLCHAR;
2343                 matchp = star_match[++star_count];
2344                 patternp += 2;
2345                 bufp++;
2346                 continue;
2347             } else if (*bufp == '\n' || *bufp == '\r') {
2348                 patternp++;
2349                 if (*patternp == NULLCHAR)
2350                   continue;
2351                 else
2352                   return FALSE;
2353             } else {
2354                 *matchp++ = *bufp++;
2355                 continue;
2356             }
2357         }
2358         if (*patternp != *bufp) return FALSE;
2359         patternp++;
2360         bufp++;
2361     }
2362 }
2363
2364 void
2365 SendToPlayer (char *data, int length)
2366 {
2367     int error, outCount;
2368     outCount = OutputToProcess(NoProc, data, length, &error);
2369     if (outCount < length) {
2370         DisplayFatalError(_("Error writing to display"), error, 1);
2371     }
2372 }
2373
2374 void
2375 PackHolding (char packed[], char *holding)
2376 {
2377     char *p = holding;
2378     char *q = packed;
2379     int runlength = 0;
2380     int curr = 9999;
2381     do {
2382         if (*p == curr) {
2383             runlength++;
2384         } else {
2385             switch (runlength) {
2386               case 0:
2387                 break;
2388               case 1:
2389                 *q++ = curr;
2390                 break;
2391               case 2:
2392                 *q++ = curr;
2393                 *q++ = curr;
2394                 break;
2395               default:
2396                 sprintf(q, "%d", runlength);
2397                 while (*q) q++;
2398                 *q++ = curr;
2399                 break;
2400             }
2401             runlength = 1;
2402             curr = *p;
2403         }
2404     } while (*p++);
2405     *q = NULLCHAR;
2406 }
2407
2408 /* Telnet protocol requests from the front end */
2409 void
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2411 {
2412     unsigned char msg[3];
2413     int outCount, outError;
2414
2415     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416
2417     if (appData.debugMode) {
2418         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2419         switch (ddww) {
2420           case TN_DO:
2421             ddwwStr = "DO";
2422             break;
2423           case TN_DONT:
2424             ddwwStr = "DONT";
2425             break;
2426           case TN_WILL:
2427             ddwwStr = "WILL";
2428             break;
2429           case TN_WONT:
2430             ddwwStr = "WONT";
2431             break;
2432           default:
2433             ddwwStr = buf1;
2434             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435             break;
2436         }
2437         switch (option) {
2438           case TN_ECHO:
2439             optionStr = "ECHO";
2440             break;
2441           default:
2442             optionStr = buf2;
2443             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2444             break;
2445         }
2446         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2447     }
2448     msg[0] = TN_IAC;
2449     msg[1] = ddww;
2450     msg[2] = option;
2451     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452     if (outCount < 3) {
2453         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2454     }
2455 }
2456
2457 void
2458 DoEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DO, TN_ECHO);
2462 }
2463
2464 void
2465 DontEcho ()
2466 {
2467     if (!appData.icsActive) return;
2468     TelnetRequest(TN_DONT, TN_ECHO);
2469 }
2470
2471 void
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 {
2474     /* put the holdings sent to us by the server on the board holdings area */
2475     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2476     char p;
2477     ChessSquare piece;
2478
2479     if(gameInfo.holdingsWidth < 2)  return;
2480     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481         return; // prevent overwriting by pre-board holdings
2482
2483     if( (int)lowestPiece >= BlackPawn ) {
2484         holdingsColumn = 0;
2485         countsColumn = 1;
2486         holdingsStartRow = BOARD_HEIGHT-1;
2487         direction = -1;
2488     } else {
2489         holdingsColumn = BOARD_WIDTH-1;
2490         countsColumn = BOARD_WIDTH-2;
2491         holdingsStartRow = 0;
2492         direction = 1;
2493     }
2494
2495     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496         board[i][holdingsColumn] = EmptySquare;
2497         board[i][countsColumn]   = (ChessSquare) 0;
2498     }
2499     while( (p=*holdings++) != NULLCHAR ) {
2500         piece = CharToPiece( ToUpper(p) );
2501         if(piece == EmptySquare) continue;
2502         /*j = (int) piece - (int) WhitePawn;*/
2503         j = PieceToNumber(piece);
2504         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505         if(j < 0) continue;               /* should not happen */
2506         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508         board[holdingsStartRow+j*direction][countsColumn]++;
2509     }
2510 }
2511
2512
2513 void
2514 VariantSwitch (Board board, VariantClass newVariant)
2515 {
2516    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517    static Board oldBoard;
2518
2519    startedFromPositionFile = FALSE;
2520    if(gameInfo.variant == newVariant) return;
2521
2522    /* [HGM] This routine is called each time an assignment is made to
2523     * gameInfo.variant during a game, to make sure the board sizes
2524     * are set to match the new variant. If that means adding or deleting
2525     * holdings, we shift the playing board accordingly
2526     * This kludge is needed because in ICS observe mode, we get boards
2527     * of an ongoing game without knowing the variant, and learn about the
2528     * latter only later. This can be because of the move list we requested,
2529     * in which case the game history is refilled from the beginning anyway,
2530     * but also when receiving holdings of a crazyhouse game. In the latter
2531     * case we want to add those holdings to the already received position.
2532     */
2533
2534
2535    if (appData.debugMode) {
2536      fprintf(debugFP, "Switch board from %s to %s\n",
2537              VariantName(gameInfo.variant), VariantName(newVariant));
2538      setbuf(debugFP, NULL);
2539    }
2540    shuffleOpenings = 0;       /* [HGM] shuffle */
2541    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2542    switch(newVariant)
2543      {
2544      case VariantShogi:
2545        newWidth = 9;  newHeight = 9;
2546        gameInfo.holdingsSize = 7;
2547      case VariantBughouse:
2548      case VariantCrazyhouse:
2549        newHoldingsWidth = 2; break;
2550      case VariantGreat:
2551        newWidth = 10;
2552      case VariantSuper:
2553        newHoldingsWidth = 2;
2554        gameInfo.holdingsSize = 8;
2555        break;
2556      case VariantGothic:
2557      case VariantCapablanca:
2558      case VariantCapaRandom:
2559        newWidth = 10;
2560      default:
2561        newHoldingsWidth = gameInfo.holdingsSize = 0;
2562      };
2563
2564    if(newWidth  != gameInfo.boardWidth  ||
2565       newHeight != gameInfo.boardHeight ||
2566       newHoldingsWidth != gameInfo.holdingsWidth ) {
2567
2568      /* shift position to new playing area, if needed */
2569      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570        for(i=0; i<BOARD_HEIGHT; i++)
2571          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573              board[i][j];
2574        for(i=0; i<newHeight; i++) {
2575          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577        }
2578      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579        for(i=0; i<BOARD_HEIGHT; i++)
2580          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2582              board[i][j];
2583      }
2584      board[HOLDINGS_SET] = 0;
2585      gameInfo.boardWidth  = newWidth;
2586      gameInfo.boardHeight = newHeight;
2587      gameInfo.holdingsWidth = newHoldingsWidth;
2588      gameInfo.variant = newVariant;
2589      InitDrawingSizes(-2, 0);
2590    } else gameInfo.variant = newVariant;
2591    CopyBoard(oldBoard, board);   // remember correctly formatted board
2592      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2593    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2594 }
2595
2596 static int loggedOn = FALSE;
2597
2598 /*-- Game start info cache: --*/
2599 int gs_gamenum;
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\   ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2607
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2610
2611 // [HGM] seekgraph
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2615 #define SQUARE 0x80
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2624
2625 void
2626 PlotSeekAd (int i)
2627 {
2628         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630         if(r < minRating+100 && r >=0 ) r = minRating+100;
2631         if(r > maxRating) r = maxRating;
2632         if(tc < 1.f) tc = 1.f;
2633         if(tc > 95.f) tc = 95.f;
2634         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635         y = ((double)r - minRating)/(maxRating - minRating)
2636             * (h-vMargin-squareSize/8-1) + vMargin;
2637         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638         if(strstr(seekAdList[i], " u ")) color = 1;
2639         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640            !strstr(seekAdList[i], "bullet") &&
2641            !strstr(seekAdList[i], "blitz") &&
2642            !strstr(seekAdList[i], "standard") ) color = 2;
2643         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2645 }
2646
2647 void
2648 PlotSingleSeekAd (int i)
2649 {
2650         PlotSeekAd(i);
2651 }
2652
2653 void
2654 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2655 {
2656         char buf[MSG_SIZ], *ext = "";
2657         VariantClass v = StringToVariant(type);
2658         if(strstr(type, "wild")) {
2659             ext = type + 4; // append wild number
2660             if(v == VariantFischeRandom) type = "chess960"; else
2661             if(v == VariantLoadable) type = "setup"; else
2662             type = VariantName(v);
2663         }
2664         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670             seekNrList[nrOfSeekAds] = nr;
2671             zList[nrOfSeekAds] = 0;
2672             seekAdList[nrOfSeekAds++] = StrSave(buf);
2673             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2674         }
2675 }
2676
2677 void
2678 EraseSeekDot (int i)
2679 {
2680     int x = xList[i], y = yList[i], d=squareSize/4, k;
2681     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683     // now replot every dot that overlapped
2684     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685         int xx = xList[k], yy = yList[k];
2686         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687             DrawSeekDot(xx, yy, colorList[k]);
2688     }
2689 }
2690
2691 void
2692 RemoveSeekAd (int nr)
2693 {
2694         int i;
2695         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696             EraseSeekDot(i);
2697             if(seekAdList[i]) free(seekAdList[i]);
2698             seekAdList[i] = seekAdList[--nrOfSeekAds];
2699             seekNrList[i] = seekNrList[nrOfSeekAds];
2700             ratingList[i] = ratingList[nrOfSeekAds];
2701             colorList[i]  = colorList[nrOfSeekAds];
2702             tcList[i] = tcList[nrOfSeekAds];
2703             xList[i]  = xList[nrOfSeekAds];
2704             yList[i]  = yList[nrOfSeekAds];
2705             zList[i]  = zList[nrOfSeekAds];
2706             seekAdList[nrOfSeekAds] = NULL;
2707             break;
2708         }
2709 }
2710
2711 Boolean
2712 MatchSoughtLine (char *line)
2713 {
2714     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715     int nr, base, inc, u=0; char dummy;
2716
2717     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719        (u=1) &&
2720        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2722         // match: compact and save the line
2723         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2724         return TRUE;
2725     }
2726     return FALSE;
2727 }
2728
2729 int
2730 DrawSeekGraph ()
2731 {
2732     int i;
2733     if(!seekGraphUp) return FALSE;
2734     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2736
2737     DrawSeekBackground(0, 0, w, h);
2738     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742         yy = h-1-yy;
2743         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2744         if(i%500 == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2748         }
2749     }
2750     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751     for(i=1; i<100; i+=(i<10?1:5)) {
2752         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755             char buf[MSG_SIZ];
2756             snprintf(buf, MSG_SIZ, "%d", i);
2757             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2758         }
2759     }
2760     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2761     return TRUE;
2762 }
2763
2764 int
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 {
2767     static int lastDown = 0, displayed = 0, lastSecond;
2768     if(y < 0) return FALSE;
2769     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771         if(!seekGraphUp) return FALSE;
2772         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773         DrawPosition(TRUE, NULL);
2774         return TRUE;
2775     }
2776     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777         if(click == Release || moving) return FALSE;
2778         nrOfSeekAds = 0;
2779         soughtPending = TRUE;
2780         SendToICS(ics_prefix);
2781         SendToICS("sought\n"); // should this be "sought all"?
2782     } else { // issue challenge based on clicked ad
2783         int dist = 10000; int i, closest = 0, second = 0;
2784         for(i=0; i<nrOfSeekAds; i++) {
2785             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2786             if(d < dist) { dist = d; closest = i; }
2787             second += (d - zList[i] < 120); // count in-range ads
2788             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2789         }
2790         if(dist < 120) {
2791             char buf[MSG_SIZ];
2792             second = (second > 1);
2793             if(displayed != closest || second != lastSecond) {
2794                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795                 lastSecond = second; displayed = closest;
2796             }
2797             if(click == Press) {
2798                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2799                 lastDown = closest;
2800                 return TRUE;
2801             } // on press 'hit', only show info
2802             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804             SendToICS(ics_prefix);
2805             SendToICS(buf);
2806             return TRUE; // let incoming board of started game pop down the graph
2807         } else if(click == Release) { // release 'miss' is ignored
2808             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809             if(moving == 2) { // right up-click
2810                 nrOfSeekAds = 0; // refresh graph
2811                 soughtPending = TRUE;
2812                 SendToICS(ics_prefix);
2813                 SendToICS("sought\n"); // should this be "sought all"?
2814             }
2815             return TRUE;
2816         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817         // press miss or release hit 'pop down' seek graph
2818         seekGraphUp = FALSE;
2819         DrawPosition(TRUE, NULL);
2820     }
2821     return TRUE;
2822 }
2823
2824 void
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 {
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2836
2837     static int started = STARTED_NONE;
2838     static char parse[20000];
2839     static int parse_pos = 0;
2840     static char buf[BUF_SIZE + 1];
2841     static int firstTime = TRUE, intfSet = FALSE;
2842     static ColorClass prevColor = ColorNormal;
2843     static int savingComment = FALSE;
2844     static int cmatch = 0; // continuation sequence match
2845     char *bp;
2846     char str[MSG_SIZ];
2847     int i, oldi;
2848     int buf_len;
2849     int next_out;
2850     int tkind;
2851     int backup;    /* [DM] For zippy color lines */
2852     char *p;
2853     char talker[MSG_SIZ]; // [HGM] chat
2854     int channel, collective=0;
2855
2856     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857
2858     if (appData.debugMode) {
2859       if (!error) {
2860         fprintf(debugFP, "<ICS: ");
2861         show_bytes(debugFP, data, count);
2862         fprintf(debugFP, "\n");
2863       }
2864     }
2865
2866     if (appData.debugMode) { int f = forwardMostMove;
2867         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2870     }
2871     if (count > 0) {
2872         /* If last read ended with a partial line that we couldn't parse,
2873            prepend it to the new read and try again. */
2874         if (leftover_len > 0) {
2875             for (i=0; i<leftover_len; i++)
2876               buf[i] = buf[leftover_start + i];
2877         }
2878
2879     /* copy new characters into the buffer */
2880     bp = buf + leftover_len;
2881     buf_len=leftover_len;
2882     for (i=0; i<count; i++)
2883     {
2884         // ignore these
2885         if (data[i] == '\r')
2886             continue;
2887
2888         // join lines split by ICS?
2889         if (!appData.noJoin)
2890         {
2891             /*
2892                 Joining just consists of finding matches against the
2893                 continuation sequence, and discarding that sequence
2894                 if found instead of copying it.  So, until a match
2895                 fails, there's nothing to do since it might be the
2896                 complete sequence, and thus, something we don't want
2897                 copied.
2898             */
2899             if (data[i] == cont_seq[cmatch])
2900             {
2901                 cmatch++;
2902                 if (cmatch == strlen(cont_seq))
2903                 {
2904                     cmatch = 0; // complete match.  just reset the counter
2905
2906                     /*
2907                         it's possible for the ICS to not include the space
2908                         at the end of the last word, making our [correct]
2909                         join operation fuse two separate words.  the server
2910                         does this when the space occurs at the width setting.
2911                     */
2912                     if (!buf_len || buf[buf_len-1] != ' ')
2913                     {
2914                         *bp++ = ' ';
2915                         buf_len++;
2916                     }
2917                 }
2918                 continue;
2919             }
2920             else if (cmatch)
2921             {
2922                 /*
2923                     match failed, so we have to copy what matched before
2924                     falling through and copying this character.  In reality,
2925                     this will only ever be just the newline character, but
2926                     it doesn't hurt to be precise.
2927                 */
2928                 strncpy(bp, cont_seq, cmatch);
2929                 bp += cmatch;
2930                 buf_len += cmatch;
2931                 cmatch = 0;
2932             }
2933         }
2934
2935         // copy this char
2936         *bp++ = data[i];
2937         buf_len++;
2938     }
2939
2940         buf[buf_len] = NULLCHAR;
2941 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2942         next_out = 0;
2943         leftover_start = 0;
2944
2945         i = 0;
2946         while (i < buf_len) {
2947             /* Deal with part of the TELNET option negotiation
2948                protocol.  We refuse to do anything beyond the
2949                defaults, except that we allow the WILL ECHO option,
2950                which ICS uses to turn off password echoing when we are
2951                directly connected to it.  We reject this option
2952                if localLineEditing mode is on (always on in xboard)
2953                and we are talking to port 23, which might be a real
2954                telnet server that will try to keep WILL ECHO on permanently.
2955              */
2956             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958                 unsigned char option;
2959                 oldi = i;
2960                 switch ((unsigned char) buf[++i]) {
2961                   case TN_WILL:
2962                     if (appData.debugMode)
2963                       fprintf(debugFP, "\n<WILL ");
2964                     switch (option = (unsigned char) buf[++i]) {
2965                       case TN_ECHO:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "ECHO ");
2968                         /* Reply only if this is a change, according
2969                            to the protocol rules. */
2970                         if (remoteEchoOption) break;
2971                         if (appData.localLineEditing &&
2972                             atoi(appData.icsPort) == TN_PORT) {
2973                             TelnetRequest(TN_DONT, TN_ECHO);
2974                         } else {
2975                             EchoOff();
2976                             TelnetRequest(TN_DO, TN_ECHO);
2977                             remoteEchoOption = TRUE;
2978                         }
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", option);
2983                         /* Whatever this is, we don't want it. */
2984                         TelnetRequest(TN_DONT, option);
2985                         break;
2986                     }
2987                     break;
2988                   case TN_WONT:
2989                     if (appData.debugMode)
2990                       fprintf(debugFP, "\n<WONT ");
2991                     switch (option = (unsigned char) buf[++i]) {
2992                       case TN_ECHO:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "ECHO ");
2995                         /* Reply only if this is a change, according
2996                            to the protocol rules. */
2997                         if (!remoteEchoOption) break;
2998                         EchoOn();
2999                         TelnetRequest(TN_DONT, TN_ECHO);
3000                         remoteEchoOption = FALSE;
3001                         break;
3002                       default:
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", (unsigned char) option);
3005                         /* Whatever this is, it must already be turned
3006                            off, because we never agree to turn on
3007                            anything non-default, so according to the
3008                            protocol rules, we don't reply. */
3009                         break;
3010                     }
3011                     break;
3012                   case TN_DO:
3013                     if (appData.debugMode)
3014                       fprintf(debugFP, "\n<DO ");
3015                     switch (option = (unsigned char) buf[++i]) {
3016                       default:
3017                         /* Whatever this is, we refuse to do it. */
3018                         if (appData.debugMode)
3019                           fprintf(debugFP, "%d ", option);
3020                         TelnetRequest(TN_WONT, option);
3021                         break;
3022                     }
3023                     break;
3024                   case TN_DONT:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<DONT ");
3027                     switch (option = (unsigned char) buf[++i]) {
3028                       default:
3029                         if (appData.debugMode)
3030                           fprintf(debugFP, "%d ", option);
3031                         /* Whatever this is, we are already not doing
3032                            it, because we never agree to do anything
3033                            non-default, so according to the protocol
3034                            rules, we don't reply. */
3035                         break;
3036                     }
3037                     break;
3038                   case TN_IAC:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<IAC ");
3041                     /* Doubled IAC; pass it through */
3042                     i--;
3043                     break;
3044                   default:
3045                     if (appData.debugMode)
3046                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047                     /* Drop all other telnet commands on the floor */
3048                     break;
3049                 }
3050                 if (oldi > next_out)
3051                   SendToPlayer(&buf[next_out], oldi - next_out);
3052                 if (++i > next_out)
3053                   next_out = i;
3054                 continue;
3055             }
3056
3057             /* OK, this at least will *usually* work */
3058             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3059                 loggedOn = TRUE;
3060             }
3061
3062             if (loggedOn && !intfSet) {
3063                 if (ics_type == ICS_ICC) {
3064                   snprintf(str, MSG_SIZ,
3065                           "/set-quietly interface %s\n/set-quietly style 12\n",
3066                           programVersion);
3067                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3069                 } else if (ics_type == ICS_CHESSNET) {
3070                   snprintf(str, MSG_SIZ, "/style 12\n");
3071                 } else {
3072                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073                   strcat(str, programVersion);
3074                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 #ifdef WIN32
3078                   strcat(str, "$iset nohighlight 1\n");
3079 #endif
3080                   strcat(str, "$iset lock 1\n$style 12\n");
3081                 }
3082                 SendToICS(str);
3083                 NotifyFrontendLogin();
3084                 intfSet = TRUE;
3085             }
3086
3087             if (started == STARTED_COMMENT) {
3088                 /* Accumulate characters in comment */
3089                 parse[parse_pos++] = buf[i];
3090                 if (buf[i] == '\n') {
3091                     parse[parse_pos] = NULLCHAR;
3092                     if(chattingPartner>=0) {
3093                         char mess[MSG_SIZ];
3094                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095                         OutputChatMessage(chattingPartner, mess);
3096                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097                             int p;
3098                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101                                 OutputChatMessage(p, mess);
3102                                 break;
3103                             }
3104                         }
3105                         chattingPartner = -1;
3106                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3107                         collective = 0;
3108                     } else
3109                     if(!suppressKibitz) // [HGM] kibitz
3110                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112                         int nrDigit = 0, nrAlph = 0, j;
3113                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115                         parse[parse_pos] = NULLCHAR;
3116                         // try to be smart: if it does not look like search info, it should go to
3117                         // ICS interaction window after all, not to engine-output window.
3118                         for(j=0; j<parse_pos; j++) { // count letters and digits
3119                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3121                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3122                         }
3123                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124                             int depth=0; float score;
3125                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127                                 pvInfoList[forwardMostMove-1].depth = depth;
3128                                 pvInfoList[forwardMostMove-1].score = 100*score;
3129                             }
3130                             OutputKibitz(suppressKibitz, parse);
3131                         } else {
3132                             char tmp[MSG_SIZ];
3133                             if(gameMode == IcsObserving) // restore original ICS messages
3134                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136                             else
3137                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139                             SendToPlayer(tmp, strlen(tmp));
3140                         }
3141                         next_out = i+1; // [HGM] suppress printing in ICS window
3142                     }
3143                     started = STARTED_NONE;
3144                 } else {
3145                     /* Don't match patterns against characters in comment */
3146                     i++;
3147                     continue;
3148                 }
3149             }
3150             if (started == STARTED_CHATTER) {
3151                 if (buf[i] != '\n') {
3152                     /* Don't match patterns against characters in chatter */
3153                     i++;
3154                     continue;
3155                 }
3156                 started = STARTED_NONE;
3157                 if(suppressKibitz) next_out = i+1;
3158             }
3159
3160             /* Kludge to deal with rcmd protocol */
3161             if (firstTime && looking_at(buf, &i, "\001*")) {
3162                 DisplayFatalError(&buf[1], 0, 1);
3163                 continue;
3164             } else {
3165                 firstTime = FALSE;
3166             }
3167
3168             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3169                 ics_type = ICS_ICC;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176                 ics_type = ICS_FICS;
3177                 ics_prefix = "$";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183                 ics_type = ICS_CHESSNET;
3184                 ics_prefix = "/";
3185                 if (appData.debugMode)
3186                   fprintf(debugFP, "ics_type %d\n", ics_type);
3187                 continue;
3188             }
3189
3190             if (!loggedOn &&
3191                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3193                  looking_at(buf, &i, "will be \"*\""))) {
3194               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3195               continue;
3196             }
3197
3198             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199               char buf[MSG_SIZ];
3200               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201               DisplayIcsInteractionTitle(buf);
3202               have_set_title = TRUE;
3203             }
3204
3205             /* skip finger notes */
3206             if (started == STARTED_NONE &&
3207                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208                  (buf[i] == '1' && buf[i+1] == '0')) &&
3209                 buf[i+2] == ':' && buf[i+3] == ' ') {
3210               started = STARTED_CHATTER;
3211               i += 3;
3212               continue;
3213             }
3214
3215             oldi = i;
3216             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217             if(appData.seekGraph) {
3218                 if(soughtPending && MatchSoughtLine(buf+i)) {
3219                     i = strstr(buf+i, "rated") - buf;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     next_out = leftover_start = i;
3222                     started = STARTED_CHATTER;
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227                         && looking_at(buf, &i, "* ads displayed")) {
3228                     soughtPending = FALSE;
3229                     seekGraphUp = TRUE;
3230                     DrawSeekGraph();
3231                     continue;
3232                 }
3233                 if(appData.autoRefresh) {
3234                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235                         int s = (ics_type == ICS_ICC); // ICC format differs
3236                         if(seekGraphUp)
3237                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239                         looking_at(buf, &i, "*% "); // eat prompt
3240                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = i; // suppress
3243                         continue;
3244                     }
3245                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246                         char *p = star_match[0];
3247                         while(*p) {
3248                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3249                             while(*p && *p++ != ' '); // next
3250                         }
3251                         looking_at(buf, &i, "*% "); // eat prompt
3252                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = i;
3254                         continue;
3255                     }
3256                 }
3257             }
3258
3259             /* skip formula vars */
3260             if (started == STARTED_NONE &&
3261                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262               started = STARTED_CHATTER;
3263               i += 3;
3264               continue;
3265             }
3266
3267             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268             if (appData.autoKibitz && started == STARTED_NONE &&
3269                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3270                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3275                         suppressKibitz = TRUE;
3276                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                         next_out = i;
3278                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279                                 && (gameMode == IcsPlayingWhite)) ||
3280                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3282                             started = STARTED_CHATTER; // own kibitz we simply discard
3283                         else {
3284                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285                             parse_pos = 0; parse[0] = NULLCHAR;
3286                             savingComment = TRUE;
3287                             suppressKibitz = gameMode != IcsObserving ? 2 :
3288                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3289                         }
3290                         continue;
3291                 } else
3292                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294                          && atoi(star_match[0])) {
3295                     // suppress the acknowledgements of our own autoKibitz
3296                     char *p;
3297                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299                     SendToPlayer(star_match[0], strlen(star_match[0]));
3300                     if(looking_at(buf, &i, "*% ")) // eat prompt
3301                         suppressKibitz = FALSE;
3302                     next_out = i;
3303                     continue;
3304                 }
3305             } // [HGM] kibitz: end of patch
3306
3307             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308
3309             // [HGM] chat: intercept tells by users for which we have an open chat window
3310             channel = -1;
3311             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312                                            looking_at(buf, &i, "* whispers:") ||
3313                                            looking_at(buf, &i, "* kibitzes:") ||
3314                                            looking_at(buf, &i, "* shouts:") ||
3315                                            looking_at(buf, &i, "* c-shouts:") ||
3316                                            looking_at(buf, &i, "--> * ") ||
3317                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321                 int p;
3322                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323                 chattingPartner = -1; collective = 0;
3324
3325                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326                 for(p=0; p<MAX_CHAT; p++) {
3327                     collective = 1;
3328                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329                     talker[0] = '['; strcat(talker, "] ");
3330                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331                     chattingPartner = p; break;
3332                     }
3333                 } else
3334                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(!strcmp("kibitzes", chatPartner[p])) {
3338                         talker[0] = '['; strcat(talker, "] ");
3339                         chattingPartner = p; break;
3340                     }
3341                 } else
3342                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343                 for(p=0; p<MAX_CHAT; p++) {
3344                     collective = 1;
3345                     if(!strcmp("whispers", chatPartner[p])) {
3346                         talker[0] = '['; strcat(talker, "] ");
3347                         chattingPartner = p; break;
3348                     }
3349                 } else
3350                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351                   if(buf[i-8] == '-' && buf[i-3] == 't')
3352                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353                     collective = 1;
3354                     if(!strcmp("c-shouts", chatPartner[p])) {
3355                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                   if(chattingPartner < 0)
3360                   for(p=0; p<MAX_CHAT; p++) {
3361                     collective = 1;
3362                     if(!strcmp("shouts", chatPartner[p])) {
3363                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366                         chattingPartner = p; break;
3367                     }
3368                   }
3369                 }
3370                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372                     talker[0] = 0;
3373                     Colorize(ColorTell, FALSE);
3374                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375                     collective |= 2;
3376                     chattingPartner = p; break;
3377                 }
3378                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380                     started = STARTED_COMMENT;
3381                     parse_pos = 0; parse[0] = NULLCHAR;
3382                     savingComment = 3 + chattingPartner; // counts as TRUE
3383                     if(collective == 3) i = oldi; else {
3384                         suppressKibitz = TRUE;
3385                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3387                         continue;
3388                     }
3389                 }
3390             } // [HGM] chat: end of patch
3391
3392           backup = i;
3393             if (appData.zippyTalk || appData.zippyPlay) {
3394                 /* [DM] Backup address for color zippy lines */
3395 #if ZIPPY
3396                if (loggedOn == TRUE)
3397                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3399 #endif
3400             } // [DM] 'else { ' deleted
3401                 if (
3402                     /* Regular tells and says */
3403                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3404                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3405                     looking_at(buf, &i, "* says: ") ||
3406                     /* Don't color "message" or "messages" output */
3407                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3408                     looking_at(buf, &i, "*. * at *:*: ") ||
3409                     looking_at(buf, &i, "--* (*:*): ") ||
3410                     /* Message notifications (same color as tells) */
3411                     looking_at(buf, &i, "* has left a message ") ||
3412                     looking_at(buf, &i, "* just sent you a message:\n") ||
3413                     /* Whispers and kibitzes */
3414                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3415                     looking_at(buf, &i, "* kibitzes: ") ||
3416                     /* Channel tells */
3417                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3418
3419                   if (tkind == 1 && strchr(star_match[0], ':')) {
3420                       /* Avoid "tells you:" spoofs in channels */
3421                      tkind = 3;
3422                   }
3423                   if (star_match[0][0] == NULLCHAR ||
3424                       strchr(star_match[0], ' ') ||
3425                       (tkind == 3 && strchr(star_match[1], ' '))) {
3426                     /* Reject bogus matches */
3427                     i = oldi;
3428                   } else {
3429                     if (appData.colorize) {
3430                       if (oldi > next_out) {
3431                         SendToPlayer(&buf[next_out], oldi - next_out);
3432                         next_out = oldi;
3433                       }
3434                       switch (tkind) {
3435                       case 1:
3436                         Colorize(ColorTell, FALSE);
3437                         curColor = ColorTell;
3438                         break;
3439                       case 2:
3440                         Colorize(ColorKibitz, FALSE);
3441                         curColor = ColorKibitz;
3442                         break;
3443                       case 3:
3444                         p = strrchr(star_match[1], '(');
3445                         if (p == NULL) {
3446                           p = star_match[1];
3447                         } else {
3448                           p++;
3449                         }
3450                         if (atoi(p) == 1) {
3451                           Colorize(ColorChannel1, FALSE);
3452                           curColor = ColorChannel1;
3453                         } else {
3454                           Colorize(ColorChannel, FALSE);
3455                           curColor = ColorChannel;
3456                         }
3457                         break;
3458                       case 5:
3459                         curColor = ColorNormal;
3460                         break;
3461                       }
3462                     }
3463                     if (started == STARTED_NONE && appData.autoComment &&
3464                         (gameMode == IcsObserving ||
3465                          gameMode == IcsPlayingWhite ||
3466                          gameMode == IcsPlayingBlack)) {
3467                       parse_pos = i - oldi;
3468                       memcpy(parse, &buf[oldi], parse_pos);
3469                       parse[parse_pos] = NULLCHAR;
3470                       started = STARTED_COMMENT;
3471                       savingComment = TRUE;
3472                     } else if(collective != 3) {
3473                       started = STARTED_CHATTER;
3474                       savingComment = FALSE;
3475                     }
3476                     loggedOn = TRUE;
3477                     continue;
3478                   }
3479                 }
3480
3481                 if (looking_at(buf, &i, "* s-shouts: ") ||
3482                     looking_at(buf, &i, "* c-shouts: ")) {
3483                     if (appData.colorize) {
3484                         if (oldi > next_out) {
3485                             SendToPlayer(&buf[next_out], oldi - next_out);
3486                             next_out = oldi;
3487                         }
3488                         Colorize(ColorSShout, FALSE);
3489                         curColor = ColorSShout;
3490                     }
3491                     loggedOn = TRUE;
3492                     started = STARTED_CHATTER;
3493                     continue;
3494                 }
3495
3496                 if (looking_at(buf, &i, "--->")) {
3497                     loggedOn = TRUE;
3498                     continue;
3499                 }
3500
3501                 if (looking_at(buf, &i, "* shouts: ") ||
3502                     looking_at(buf, &i, "--> ")) {
3503                     if (appData.colorize) {
3504                         if (oldi > next_out) {
3505                             SendToPlayer(&buf[next_out], oldi - next_out);
3506                             next_out = oldi;
3507                         }
3508                         Colorize(ColorShout, FALSE);
3509                         curColor = ColorShout;
3510                     }
3511                     loggedOn = TRUE;
3512                     started = STARTED_CHATTER;
3513                     continue;
3514                 }
3515
3516                 if (looking_at( buf, &i, "Challenge:")) {
3517                     if (appData.colorize) {
3518                         if (oldi > next_out) {
3519                             SendToPlayer(&buf[next_out], oldi - next_out);
3520                             next_out = oldi;
3521                         }
3522                         Colorize(ColorChallenge, FALSE);
3523                         curColor = ColorChallenge;
3524                     }
3525                     loggedOn = TRUE;
3526                     continue;
3527                 }
3528
3529                 if (looking_at(buf, &i, "* offers you") ||
3530                     looking_at(buf, &i, "* offers to be") ||
3531                     looking_at(buf, &i, "* would like to") ||
3532                     looking_at(buf, &i, "* requests to") ||
3533                     looking_at(buf, &i, "Your opponent offers") ||
3534                     looking_at(buf, &i, "Your opponent requests")) {
3535
3536                     if (appData.colorize) {
3537                         if (oldi > next_out) {
3538                             SendToPlayer(&buf[next_out], oldi - next_out);
3539                             next_out = oldi;
3540                         }
3541                         Colorize(ColorRequest, FALSE);
3542                         curColor = ColorRequest;
3543                     }
3544                     continue;
3545                 }
3546
3547                 if (looking_at(buf, &i, "* (*) seeking")) {
3548                     if (appData.colorize) {
3549                         if (oldi > next_out) {
3550                             SendToPlayer(&buf[next_out], oldi - next_out);
3551                             next_out = oldi;
3552                         }
3553                         Colorize(ColorSeek, FALSE);
3554                         curColor = ColorSeek;
3555                     }
3556                     continue;
3557             }
3558
3559           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3560
3561             if (looking_at(buf, &i, "\\   ")) {
3562                 if (prevColor != ColorNormal) {
3563                     if (oldi > next_out) {
3564                         SendToPlayer(&buf[next_out], oldi - next_out);
3565                         next_out = oldi;
3566                     }
3567                     Colorize(prevColor, TRUE);
3568                     curColor = prevColor;
3569                 }
3570                 if (savingComment) {
3571                     parse_pos = i - oldi;
3572                     memcpy(parse, &buf[oldi], parse_pos);
3573                     parse[parse_pos] = NULLCHAR;
3574                     started = STARTED_COMMENT;
3575                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3576                         chattingPartner = savingComment - 3; // kludge to remember the box
3577                 } else {
3578                     started = STARTED_CHATTER;
3579                 }
3580                 continue;
3581             }
3582
3583             if (looking_at(buf, &i, "Black Strength :") ||
3584                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3585                 looking_at(buf, &i, "<10>") ||
3586                 looking_at(buf, &i, "#@#")) {
3587                 /* Wrong board style */
3588                 loggedOn = TRUE;
3589                 SendToICS(ics_prefix);
3590                 SendToICS("set style 12\n");
3591                 SendToICS(ics_prefix);
3592                 SendToICS("refresh\n");
3593                 continue;
3594             }
3595
3596             if (looking_at(buf, &i, "login:")) {
3597               if (!have_sent_ICS_logon) {
3598                 if(ICSInitScript())
3599                   have_sent_ICS_logon = 1;
3600                 else // no init script was found
3601                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3602               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3603                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3604               }
3605                 continue;
3606             }
3607
3608             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3609                 (looking_at(buf, &i, "\n<12> ") ||
3610                  looking_at(buf, &i, "<12> "))) {
3611                 loggedOn = TRUE;
3612                 if (oldi > next_out) {
3613                     SendToPlayer(&buf[next_out], oldi - next_out);
3614                 }
3615                 next_out = i;
3616                 started = STARTED_BOARD;
3617                 parse_pos = 0;
3618                 continue;
3619             }
3620
3621             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3622                 looking_at(buf, &i, "<b1> ")) {
3623                 if (oldi > next_out) {
3624                     SendToPlayer(&buf[next_out], oldi - next_out);
3625                 }
3626                 next_out = i;
3627                 started = STARTED_HOLDINGS;
3628                 parse_pos = 0;
3629                 continue;
3630             }
3631
3632             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3633                 loggedOn = TRUE;
3634                 /* Header for a move list -- first line */
3635
3636                 switch (ics_getting_history) {
3637                   case H_FALSE:
3638                     switch (gameMode) {
3639                       case IcsIdle:
3640                       case BeginningOfGame:
3641                         /* User typed "moves" or "oldmoves" while we
3642                            were idle.  Pretend we asked for these
3643                            moves and soak them up so user can step
3644                            through them and/or save them.
3645                            */
3646                         Reset(FALSE, TRUE);
3647                         gameMode = IcsObserving;
3648                         ModeHighlight();
3649                         ics_gamenum = -1;
3650                         ics_getting_history = H_GOT_UNREQ_HEADER;
3651                         break;
3652                       case EditGame: /*?*/
3653                       case EditPosition: /*?*/
3654                         /* Should above feature work in these modes too? */
3655                         /* For now it doesn't */
3656                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3657                         break;
3658                       default:
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                     }
3662                     break;
3663                   case H_REQUESTED:
3664                     /* Is this the right one? */
3665                     if (gameInfo.white && gameInfo.black &&
3666                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3667                         strcmp(gameInfo.black, star_match[2]) == 0) {
3668                         /* All is well */
3669                         ics_getting_history = H_GOT_REQ_HEADER;
3670                     }
3671                     break;
3672                   case H_GOT_REQ_HEADER:
3673                   case H_GOT_UNREQ_HEADER:
3674                   case H_GOT_UNWANTED_HEADER:
3675                   case H_GETTING_MOVES:
3676                     /* Should not happen */
3677                     DisplayError(_("Error gathering move list: two headers"), 0);
3678                     ics_getting_history = H_FALSE;
3679                     break;
3680                 }
3681
3682                 /* Save player ratings into gameInfo if needed */
3683                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3684                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3685                     (gameInfo.whiteRating == -1 ||
3686                      gameInfo.blackRating == -1)) {
3687
3688                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3689                     gameInfo.blackRating = string_to_rating(star_match[3]);
3690                     if (appData.debugMode)
3691                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3692                               gameInfo.whiteRating, gameInfo.blackRating);
3693                 }
3694                 continue;
3695             }
3696
3697             if (looking_at(buf, &i,
3698               "* * match, initial time: * minute*, increment: * second")) {
3699                 /* Header for a move list -- second line */
3700                 /* Initial board will follow if this is a wild game */
3701                 if (gameInfo.event != NULL) free(gameInfo.event);
3702                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3703                 gameInfo.event = StrSave(str);
3704                 /* [HGM] we switched variant. Translate boards if needed. */
3705                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3706                 continue;
3707             }
3708
3709             if (looking_at(buf, &i, "Move  ")) {
3710                 /* Beginning of a move list */
3711                 switch (ics_getting_history) {
3712                   case H_FALSE:
3713                     /* Normally should not happen */
3714                     /* Maybe user hit reset while we were parsing */
3715                     break;
3716                   case H_REQUESTED:
3717                     /* Happens if we are ignoring a move list that is not
3718                      * the one we just requested.  Common if the user
3719                      * tries to observe two games without turning off
3720                      * getMoveList */
3721                     break;
3722                   case H_GETTING_MOVES:
3723                     /* Should not happen */
3724                     DisplayError(_("Error gathering move list: nested"), 0);
3725                     ics_getting_history = H_FALSE;
3726                     break;
3727                   case H_GOT_REQ_HEADER:
3728                     ics_getting_history = H_GETTING_MOVES;
3729                     started = STARTED_MOVES;
3730                     parse_pos = 0;
3731                     if (oldi > next_out) {
3732                         SendToPlayer(&buf[next_out], oldi - next_out);
3733                     }
3734                     break;
3735                   case H_GOT_UNREQ_HEADER:
3736                     ics_getting_history = H_GETTING_MOVES;
3737                     started = STARTED_MOVES_NOHIDE;
3738                     parse_pos = 0;
3739                     break;
3740                   case H_GOT_UNWANTED_HEADER:
3741                     ics_getting_history = H_FALSE;
3742                     break;
3743                 }
3744                 continue;
3745             }
3746
3747             if (looking_at(buf, &i, "% ") ||
3748                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3749                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3750                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3751                     soughtPending = FALSE;
3752                     seekGraphUp = TRUE;
3753                     DrawSeekGraph();
3754                 }
3755                 if(suppressKibitz) next_out = i;
3756                 savingComment = FALSE;
3757                 suppressKibitz = 0;
3758                 switch (started) {
3759                   case STARTED_MOVES:
3760                   case STARTED_MOVES_NOHIDE:
3761                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3762                     parse[parse_pos + i - oldi] = NULLCHAR;
3763                     ParseGameHistory(parse);
3764 #if ZIPPY
3765                     if (appData.zippyPlay && first.initDone) {
3766                         FeedMovesToProgram(&first, forwardMostMove);
3767                         if (gameMode == IcsPlayingWhite) {
3768                             if (WhiteOnMove(forwardMostMove)) {
3769                                 if (first.sendTime) {
3770                                   if (first.useColors) {
3771                                     SendToProgram("black\n", &first);
3772                                   }
3773                                   SendTimeRemaining(&first, TRUE);
3774                                 }
3775                                 if (first.useColors) {
3776                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3777                                 }
3778                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3779                                 first.maybeThinking = TRUE;
3780                             } else {
3781                                 if (first.usePlayother) {
3782                                   if (first.sendTime) {
3783                                     SendTimeRemaining(&first, TRUE);
3784                                   }
3785                                   SendToProgram("playother\n", &first);
3786                                   firstMove = FALSE;
3787                                 } else {
3788                                   firstMove = TRUE;
3789                                 }
3790                             }
3791                         } else if (gameMode == IcsPlayingBlack) {
3792                             if (!WhiteOnMove(forwardMostMove)) {
3793                                 if (first.sendTime) {
3794                                   if (first.useColors) {
3795                                     SendToProgram("white\n", &first);
3796                                   }
3797                                   SendTimeRemaining(&first, FALSE);
3798                                 }
3799                                 if (first.useColors) {
3800                                   SendToProgram("black\n", &first);
3801                                 }
3802                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3803                                 first.maybeThinking = TRUE;
3804                             } else {
3805                                 if (first.usePlayother) {
3806                                   if (first.sendTime) {
3807                                     SendTimeRemaining(&first, FALSE);
3808                                   }
3809                                   SendToProgram("playother\n", &first);
3810                                   firstMove = FALSE;
3811                                 } else {
3812                                   firstMove = TRUE;
3813                                 }
3814                             }
3815                         }
3816                     }
3817 #endif
3818                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3819                         /* Moves came from oldmoves or moves command
3820                            while we weren't doing anything else.
3821                            */
3822                         currentMove = forwardMostMove;
3823                         ClearHighlights();/*!!could figure this out*/
3824                         flipView = appData.flipView;
3825                         DrawPosition(TRUE, boards[currentMove]);
3826                         DisplayBothClocks();
3827                         snprintf(str, MSG_SIZ, "%s %s %s",
3828                                 gameInfo.white, _("vs."),  gameInfo.black);
3829                         DisplayTitle(str);
3830                         gameMode = IcsIdle;
3831                     } else {
3832                         /* Moves were history of an active game */
3833                         if (gameInfo.resultDetails != NULL) {
3834                             free(gameInfo.resultDetails);
3835                             gameInfo.resultDetails = NULL;
3836                         }
3837                     }
3838                     HistorySet(parseList, backwardMostMove,
3839                                forwardMostMove, currentMove-1);
3840                     DisplayMove(currentMove - 1);
3841                     if (started == STARTED_MOVES) next_out = i;
3842                     started = STARTED_NONE;
3843                     ics_getting_history = H_FALSE;
3844                     break;
3845
3846                   case STARTED_OBSERVE:
3847                     started = STARTED_NONE;
3848                     SendToICS(ics_prefix);
3849                     SendToICS("refresh\n");
3850                     break;
3851
3852                   default:
3853                     break;
3854                 }
3855                 if(bookHit) { // [HGM] book: simulate book reply
3856                     static char bookMove[MSG_SIZ]; // a bit generous?
3857
3858                     programStats.nodes = programStats.depth = programStats.time =
3859                     programStats.score = programStats.got_only_move = 0;
3860                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3861
3862                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3863                     strcat(bookMove, bookHit);
3864                     HandleMachineMove(bookMove, &first);
3865                 }
3866                 continue;
3867             }
3868
3869             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3870                  started == STARTED_HOLDINGS ||
3871                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3872                 /* Accumulate characters in move list or board */
3873                 parse[parse_pos++] = buf[i];
3874             }
3875
3876             /* Start of game messages.  Mostly we detect start of game
3877                when the first board image arrives.  On some versions
3878                of the ICS, though, we need to do a "refresh" after starting
3879                to observe in order to get the current board right away. */
3880             if (looking_at(buf, &i, "Adding game * to observation list")) {
3881                 started = STARTED_OBSERVE;
3882                 continue;
3883             }
3884
3885             /* Handle auto-observe */
3886             if (appData.autoObserve &&
3887                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3888                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3889                 char *player;
3890                 /* Choose the player that was highlighted, if any. */
3891                 if (star_match[0][0] == '\033' ||
3892                     star_match[1][0] != '\033') {
3893                     player = star_match[0];
3894                 } else {
3895                     player = star_match[2];
3896                 }
3897                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3898                         ics_prefix, StripHighlightAndTitle(player));
3899                 SendToICS(str);
3900
3901                 /* Save ratings from notify string */
3902                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3903                 player1Rating = string_to_rating(star_match[1]);
3904                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3905                 player2Rating = string_to_rating(star_match[3]);
3906
3907                 if (appData.debugMode)
3908                   fprintf(debugFP,
3909                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3910                           player1Name, player1Rating,
3911                           player2Name, player2Rating);
3912
3913                 continue;
3914             }
3915
3916             /* Deal with automatic examine mode after a game,
3917                and with IcsObserving -> IcsExamining transition */
3918             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3919                 looking_at(buf, &i, "has made you an examiner of game *")) {
3920
3921                 int gamenum = atoi(star_match[0]);
3922                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3923                     gamenum == ics_gamenum) {
3924                     /* We were already playing or observing this game;
3925                        no need to refetch history */
3926                     gameMode = IcsExamining;
3927                     if (pausing) {
3928                         pauseExamForwardMostMove = forwardMostMove;
3929                     } else if (currentMove < forwardMostMove) {
3930                         ForwardInner(forwardMostMove);
3931                     }
3932                 } else {
3933                     /* I don't think this case really can happen */
3934                     SendToICS(ics_prefix);
3935                     SendToICS("refresh\n");
3936                 }
3937                 continue;
3938             }
3939
3940             /* Error messages */
3941 //          if (ics_user_moved) {
3942             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3943                 if (looking_at(buf, &i, "Illegal move") ||
3944                     looking_at(buf, &i, "Not a legal move") ||
3945                     looking_at(buf, &i, "Your king is in check") ||
3946                     looking_at(buf, &i, "It isn't your turn") ||
3947                     looking_at(buf, &i, "It is not your move")) {
3948                     /* Illegal move */
3949                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3950                         currentMove = forwardMostMove-1;
3951                         DisplayMove(currentMove - 1); /* before DMError */
3952                         DrawPosition(FALSE, boards[currentMove]);
3953                         SwitchClocks(forwardMostMove-1); // [HGM] race
3954                         DisplayBothClocks();
3955                     }
3956                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3957                     ics_user_moved = 0;
3958                     continue;
3959                 }
3960             }
3961
3962             if (looking_at(buf, &i, "still have time") ||
3963                 looking_at(buf, &i, "not out of time") ||
3964                 looking_at(buf, &i, "either player is out of time") ||
3965                 looking_at(buf, &i, "has timeseal; checking")) {
3966                 /* We must have called his flag a little too soon */
3967                 whiteFlag = blackFlag = FALSE;
3968                 continue;
3969             }
3970
3971             if (looking_at(buf, &i, "added * seconds to") ||
3972                 looking_at(buf, &i, "seconds were added to")) {
3973                 /* Update the clocks */
3974                 SendToICS(ics_prefix);
3975                 SendToICS("refresh\n");
3976                 continue;
3977             }
3978
3979             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3980                 ics_clock_paused = TRUE;
3981                 StopClocks();
3982                 continue;
3983             }
3984
3985             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3986                 ics_clock_paused = FALSE;
3987                 StartClocks();
3988                 continue;
3989             }
3990
3991             /* Grab player ratings from the Creating: message.
3992                Note we have to check for the special case when
3993                the ICS inserts things like [white] or [black]. */
3994             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3995                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3996                 /* star_matches:
3997                    0    player 1 name (not necessarily white)
3998                    1    player 1 rating
3999                    2    empty, white, or black (IGNORED)
4000                    3    player 2 name (not necessarily black)
4001                    4    player 2 rating
4002
4003                    The names/ratings are sorted out when the game
4004                    actually starts (below).
4005                 */
4006                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4007                 player1Rating = string_to_rating(star_match[1]);
4008                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4009                 player2Rating = string_to_rating(star_match[4]);
4010
4011                 if (appData.debugMode)
4012                   fprintf(debugFP,
4013                           "Ratings from 'Creating:' %s %d, %s %d\n",
4014                           player1Name, player1Rating,
4015                           player2Name, player2Rating);
4016
4017                 continue;
4018             }
4019
4020             /* Improved generic start/end-of-game messages */
4021             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4022                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4023                 /* If tkind == 0: */
4024                 /* star_match[0] is the game number */
4025                 /*           [1] is the white player's name */
4026                 /*           [2] is the black player's name */
4027                 /* For end-of-game: */
4028                 /*           [3] is the reason for the game end */
4029                 /*           [4] is a PGN end game-token, preceded by " " */
4030                 /* For start-of-game: */
4031                 /*           [3] begins with "Creating" or "Continuing" */
4032                 /*           [4] is " *" or empty (don't care). */
4033                 int gamenum = atoi(star_match[0]);
4034                 char *whitename, *blackname, *why, *endtoken;
4035                 ChessMove endtype = EndOfFile;
4036
4037                 if (tkind == 0) {
4038                   whitename = star_match[1];
4039                   blackname = star_match[2];
4040                   why = star_match[3];
4041                   endtoken = star_match[4];
4042                 } else {
4043                   whitename = star_match[1];
4044                   blackname = star_match[3];
4045                   why = star_match[5];
4046                   endtoken = star_match[6];
4047                 }
4048
4049                 /* Game start messages */
4050                 if (strncmp(why, "Creating ", 9) == 0 ||
4051                     strncmp(why, "Continuing ", 11) == 0) {
4052                     gs_gamenum = gamenum;
4053                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4054                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4055                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4056 #if ZIPPY
4057                     if (appData.zippyPlay) {
4058                         ZippyGameStart(whitename, blackname);
4059                     }
4060 #endif /*ZIPPY*/
4061                     partnerBoardValid = FALSE; // [HGM] bughouse
4062                     continue;
4063                 }
4064
4065                 /* Game end messages */
4066                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4067                     ics_gamenum != gamenum) {
4068                     continue;
4069                 }
4070                 while (endtoken[0] == ' ') endtoken++;
4071                 switch (endtoken[0]) {
4072                   case '*':
4073                   default:
4074                     endtype = GameUnfinished;
4075                     break;
4076                   case '0':
4077                     endtype = BlackWins;
4078                     break;
4079                   case '1':
4080                     if (endtoken[1] == '/')
4081                       endtype = GameIsDrawn;
4082                     else
4083                       endtype = WhiteWins;
4084                     break;
4085                 }
4086                 GameEnds(endtype, why, GE_ICS);
4087 #if ZIPPY
4088                 if (appData.zippyPlay && first.initDone) {
4089                     ZippyGameEnd(endtype, why);
4090                     if (first.pr == NoProc) {
4091                       /* Start the next process early so that we'll
4092                          be ready for the next challenge */
4093                       StartChessProgram(&first);
4094                     }
4095                     /* Send "new" early, in case this command takes
4096                        a long time to finish, so that we'll be ready
4097                        for the next challenge. */
4098                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4099                     Reset(TRUE, TRUE);
4100                 }
4101 #endif /*ZIPPY*/
4102                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4103                 continue;
4104             }
4105
4106             if (looking_at(buf, &i, "Removing game * from observation") ||
4107                 looking_at(buf, &i, "no longer observing game *") ||
4108                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4109                 if (gameMode == IcsObserving &&
4110                     atoi(star_match[0]) == ics_gamenum)
4111                   {
4112                       /* icsEngineAnalyze */
4113                       if (appData.icsEngineAnalyze) {
4114                             ExitAnalyzeMode();
4115                             ModeHighlight();
4116                       }
4117                       StopClocks();
4118                       gameMode = IcsIdle;
4119                       ics_gamenum = -1;
4120                       ics_user_moved = FALSE;
4121                   }
4122                 continue;
4123             }
4124
4125             if (looking_at(buf, &i, "no longer examining game *")) {
4126                 if (gameMode == IcsExamining &&
4127                     atoi(star_match[0]) == ics_gamenum)
4128                   {
4129                       gameMode = IcsIdle;
4130                       ics_gamenum = -1;
4131                       ics_user_moved = FALSE;
4132                   }
4133                 continue;
4134             }
4135
4136             /* Advance leftover_start past any newlines we find,
4137                so only partial lines can get reparsed */
4138             if (looking_at(buf, &i, "\n")) {
4139                 prevColor = curColor;
4140                 if (curColor != ColorNormal) {
4141                     if (oldi > next_out) {
4142                         SendToPlayer(&buf[next_out], oldi - next_out);
4143                         next_out = oldi;
4144                     }
4145                     Colorize(ColorNormal, FALSE);
4146                     curColor = ColorNormal;
4147                 }
4148                 if (started == STARTED_BOARD) {
4149                     started = STARTED_NONE;
4150                     parse[parse_pos] = NULLCHAR;
4151                     ParseBoard12(parse);
4152                     ics_user_moved = 0;
4153
4154                     /* Send premove here */
4155                     if (appData.premove) {
4156                       char str[MSG_SIZ];
4157                       if (currentMove == 0 &&
4158                           gameMode == IcsPlayingWhite &&
4159                           appData.premoveWhite) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (currentMove == 1 &&
4165                                  gameMode == IcsPlayingBlack &&
4166                                  appData.premoveBlack) {
4167                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                         SendToICS(str);
4171                       } else if (gotPremove) {
4172                         int oldFMM = forwardMostMove;
4173                         gotPremove = 0;
4174                         ClearPremoveHighlights();
4175                         if (appData.debugMode)
4176                           fprintf(debugFP, "Sending premove:\n");
4177                           UserMoveEvent(premoveFromX, premoveFromY,
4178                                         premoveToX, premoveToY,
4179                                         premovePromoChar);
4180                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4181                           if(moveList[oldFMM-1][1] != '@')
4182                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4183                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4184                           else // (drop)
4185                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186                         }
4187                       }
4188                     }
4189
4190                     /* Usually suppress following prompt */
4191                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4192                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4193                         if (looking_at(buf, &i, "*% ")) {
4194                             savingComment = FALSE;
4195                             suppressKibitz = 0;
4196                         }
4197                     }
4198                     next_out = i;
4199                 } else if (started == STARTED_HOLDINGS) {
4200                     int gamenum;
4201                     char new_piece[MSG_SIZ];
4202                     started = STARTED_NONE;
4203                     parse[parse_pos] = NULLCHAR;
4204                     if (appData.debugMode)
4205                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4206                                                         parse, currentMove);
4207                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4208                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4209                         if (gameInfo.variant == VariantNormal) {
4210                           /* [HGM] We seem to switch variant during a game!
4211                            * Presumably no holdings were displayed, so we have
4212                            * to move the position two files to the right to
4213                            * create room for them!
4214                            */
4215                           VariantClass newVariant;
4216                           switch(gameInfo.boardWidth) { // base guess on board width
4217                                 case 9:  newVariant = VariantShogi; break;
4218                                 case 10: newVariant = VariantGreat; break;
4219                                 default: newVariant = VariantCrazyhouse; break;
4220                           }
4221                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4222                           /* Get a move list just to see the header, which
4223                              will tell us whether this is really bug or zh */
4224                           if (ics_getting_history == H_FALSE) {
4225                             ics_getting_history = H_REQUESTED;
4226                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4227                             SendToICS(str);
4228                           }
4229                         }
4230                         new_piece[0] = NULLCHAR;
4231                         sscanf(parse, "game %d white [%s black [%s <- %s",
4232                                &gamenum, white_holding, black_holding,
4233                                new_piece);
4234                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4235                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4236                         /* [HGM] copy holdings to board holdings area */
4237                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4238                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4239                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4240 #if ZIPPY
4241                         if (appData.zippyPlay && first.initDone) {
4242                             ZippyHoldings(white_holding, black_holding,
4243                                           new_piece);
4244                         }
4245 #endif /*ZIPPY*/
4246                         if (tinyLayout || smallLayout) {
4247                             char wh[16], bh[16];
4248                             PackHolding(wh, white_holding);
4249                             PackHolding(bh, black_holding);
4250                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4251                                     gameInfo.white, gameInfo.black);
4252                         } else {
4253                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4254                                     gameInfo.white, white_holding, _("vs."),
4255                                     gameInfo.black, black_holding);
4256                         }
4257                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4258                         DrawPosition(FALSE, boards[currentMove]);
4259                         DisplayTitle(str);
4260                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4261                         sscanf(parse, "game %d white [%s black [%s <- %s",
4262                                &gamenum, white_holding, black_holding,
4263                                new_piece);
4264                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4265                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4266                         /* [HGM] copy holdings to partner-board holdings area */
4267                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4268                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4269                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4270                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4272                       }
4273                     }
4274                     /* Suppress following prompt */
4275                     if (looking_at(buf, &i, "*% ")) {
4276                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4277                         savingComment = FALSE;
4278                         suppressKibitz = 0;
4279                     }
4280                     next_out = i;
4281                 }
4282                 continue;
4283             }
4284
4285             i++;                /* skip unparsed character and loop back */
4286         }
4287
4288         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4289 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4290 //          SendToPlayer(&buf[next_out], i - next_out);
4291             started != STARTED_HOLDINGS && leftover_start > next_out) {
4292             SendToPlayer(&buf[next_out], leftover_start - next_out);
4293             next_out = i;
4294         }
4295
4296         leftover_len = buf_len - leftover_start;
4297         /* if buffer ends with something we couldn't parse,
4298            reparse it after appending the next read */
4299
4300     } else if (count == 0) {
4301         RemoveInputSource(isr);
4302         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4303     } else {
4304         DisplayFatalError(_("Error reading from ICS"), error, 1);
4305     }
4306 }
4307
4308
4309 /* Board style 12 looks like this:
4310
4311    <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
4312
4313  * The "<12> " is stripped before it gets to this routine.  The two
4314  * trailing 0's (flip state and clock ticking) are later addition, and
4315  * some chess servers may not have them, or may have only the first.
4316  * Additional trailing fields may be added in the future.
4317  */
4318
4319 #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"
4320
4321 #define RELATION_OBSERVING_PLAYED    0
4322 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4323 #define RELATION_PLAYING_MYMOVE      1
4324 #define RELATION_PLAYING_NOTMYMOVE  -1
4325 #define RELATION_EXAMINING           2
4326 #define RELATION_ISOLATED_BOARD     -3
4327 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4328
4329 void
4330 ParseBoard12 (char *string)
4331 {
4332 #if ZIPPY
4333     int i, takeback;
4334     char *bookHit = NULL; // [HGM] book
4335 #endif
4336     GameMode newGameMode;
4337     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4338     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4339     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4340     char to_play, board_chars[200];
4341     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4342     char black[32], white[32];
4343     Board board;
4344     int prevMove = currentMove;
4345     int ticking = 2;
4346     ChessMove moveType;
4347     int fromX, fromY, toX, toY;
4348     char promoChar;
4349     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4350     Boolean weird = FALSE, reqFlag = FALSE;
4351
4352     fromX = fromY = toX = toY = -1;
4353
4354     newGame = FALSE;
4355
4356     if (appData.debugMode)
4357       fprintf(debugFP, "Parsing board: %s\n", string);
4358
4359     move_str[0] = NULLCHAR;
4360     elapsed_time[0] = NULLCHAR;
4361     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4362         int  i = 0, j;
4363         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4364             if(string[i] == ' ') { ranks++; files = 0; }
4365             else files++;
4366             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4367             i++;
4368         }
4369         for(j = 0; j <i; j++) board_chars[j] = string[j];
4370         board_chars[i] = '\0';
4371         string += i + 1;
4372     }
4373     n = sscanf(string, PATTERN, &to_play, &double_push,
4374                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4375                &gamenum, white, black, &relation, &basetime, &increment,
4376                &white_stren, &black_stren, &white_time, &black_time,
4377                &moveNum, str, elapsed_time, move_str, &ics_flip,
4378                &ticking);
4379
4380     if (n < 21) {
4381         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4382         DisplayError(str, 0);
4383         return;
4384     }
4385
4386     /* Convert the move number to internal form */
4387     moveNum = (moveNum - 1) * 2;
4388     if (to_play == 'B') moveNum++;
4389     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4390       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4391                         0, 1);
4392       return;
4393     }
4394
4395     switch (relation) {
4396       case RELATION_OBSERVING_PLAYED:
4397       case RELATION_OBSERVING_STATIC:
4398         if (gamenum == -1) {
4399             /* Old ICC buglet */
4400             relation = RELATION_OBSERVING_STATIC;
4401         }
4402         newGameMode = IcsObserving;
4403         break;
4404       case RELATION_PLAYING_MYMOVE:
4405       case RELATION_PLAYING_NOTMYMOVE:
4406         newGameMode =
4407           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4408             IcsPlayingWhite : IcsPlayingBlack;
4409         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4410         break;
4411       case RELATION_EXAMINING:
4412         newGameMode = IcsExamining;
4413         break;
4414       case RELATION_ISOLATED_BOARD:
4415       default:
4416         /* Just display this board.  If user was doing something else,
4417            we will forget about it until the next board comes. */
4418         newGameMode = IcsIdle;
4419         break;
4420       case RELATION_STARTING_POSITION:
4421         newGameMode = gameMode;
4422         break;
4423     }
4424
4425     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4426         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4427          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4428       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4429       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4430       static int lastBgGame = -1;
4431       char *toSqr;
4432       for (k = 0; k < ranks; k++) {
4433         for (j = 0; j < files; j++)
4434           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4435         if(gameInfo.holdingsWidth > 1) {
4436              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4437              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4438         }
4439       }
4440       CopyBoard(partnerBoard, board);
4441       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4442         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4443         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4444       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4445       if(toSqr = strchr(str, '-')) {
4446         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4447         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4448       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4449       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4450       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4451       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4452       if(twoBoards) {
4453           DisplayWhiteClock(white_time*fac, to_play == 'W');
4454           DisplayBlackClock(black_time*fac, to_play != 'W');
4455           activePartner = to_play;
4456           if(gamenum != lastBgGame) {
4457               char buf[MSG_SIZ];
4458               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4459               DisplayTitle(buf);
4460           }
4461           lastBgGame = gamenum;
4462           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4463                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4464       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4465                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4466       if(!twoBoards) DisplayMessage(partnerStatus, "");
4467         partnerBoardValid = TRUE;
4468       return;
4469     }
4470
4471     if(appData.dualBoard && appData.bgObserve) {
4472         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4473             SendToICS(ics_prefix), SendToICS("pobserve\n");
4474         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4475             char buf[MSG_SIZ];
4476             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4477             SendToICS(buf);
4478         }
4479     }
4480
4481     /* Modify behavior for initial board display on move listing
4482        of wild games.
4483        */
4484     switch (ics_getting_history) {
4485       case H_FALSE:
4486       case H_REQUESTED:
4487         break;
4488       case H_GOT_REQ_HEADER:
4489       case H_GOT_UNREQ_HEADER:
4490         /* This is the initial position of the current game */
4491         gamenum = ics_gamenum;
4492         moveNum = 0;            /* old ICS bug workaround */
4493         if (to_play == 'B') {
4494           startedFromSetupPosition = TRUE;
4495           blackPlaysFirst = TRUE;
4496           moveNum = 1;
4497           if (forwardMostMove == 0) forwardMostMove = 1;
4498           if (backwardMostMove == 0) backwardMostMove = 1;
4499           if (currentMove == 0) currentMove = 1;
4500         }
4501         newGameMode = gameMode;
4502         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4503         break;
4504       case H_GOT_UNWANTED_HEADER:
4505         /* This is an initial board that we don't want */
4506         return;
4507       case H_GETTING_MOVES:
4508         /* Should not happen */
4509         DisplayError(_("Error gathering move list: extra board"), 0);
4510         ics_getting_history = H_FALSE;
4511         return;
4512     }
4513
4514    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4515                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4516                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4517      /* [HGM] We seem to have switched variant unexpectedly
4518       * Try to guess new variant from board size
4519       */
4520           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4521           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4522           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4523           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4524           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4525           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4526           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4527           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4528           /* Get a move list just to see the header, which
4529              will tell us whether this is really bug or zh */
4530           if (ics_getting_history == H_FALSE) {
4531             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534           }
4535     }
4536
4537     /* Take action if this is the first board of a new game, or of a
4538        different game than is currently being displayed.  */
4539     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4540         relation == RELATION_ISOLATED_BOARD) {
4541
4542         /* Forget the old game and get the history (if any) of the new one */
4543         if (gameMode != BeginningOfGame) {
4544           Reset(TRUE, TRUE);
4545         }
4546         newGame = TRUE;
4547         if (appData.autoRaiseBoard) BoardToTop();
4548         prevMove = -3;
4549         if (gamenum == -1) {
4550             newGameMode = IcsIdle;
4551         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4552                    appData.getMoveList && !reqFlag) {
4553             /* Need to get game history */
4554             ics_getting_history = H_REQUESTED;
4555             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4556             SendToICS(str);
4557         }
4558
4559         /* Initially flip the board to have black on the bottom if playing
4560            black or if the ICS flip flag is set, but let the user change
4561            it with the Flip View button. */
4562         flipView = appData.autoFlipView ?
4563           (newGameMode == IcsPlayingBlack) || ics_flip :
4564           appData.flipView;
4565
4566         /* Done with values from previous mode; copy in new ones */
4567         gameMode = newGameMode;
4568         ModeHighlight();
4569         ics_gamenum = gamenum;
4570         if (gamenum == gs_gamenum) {
4571             int klen = strlen(gs_kind);
4572             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4573             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4574             gameInfo.event = StrSave(str);
4575         } else {
4576             gameInfo.event = StrSave("ICS game");
4577         }
4578         gameInfo.site = StrSave(appData.icsHost);
4579         gameInfo.date = PGNDate();
4580         gameInfo.round = StrSave("-");
4581         gameInfo.white = StrSave(white);
4582         gameInfo.black = StrSave(black);
4583         timeControl = basetime * 60 * 1000;
4584         timeControl_2 = 0;
4585         timeIncrement = increment * 1000;
4586         movesPerSession = 0;
4587         gameInfo.timeControl = TimeControlTagValue();
4588         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4589   if (appData.debugMode) {
4590     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4591     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4592     setbuf(debugFP, NULL);
4593   }
4594
4595         gameInfo.outOfBook = NULL;
4596
4597         /* Do we have the ratings? */
4598         if (strcmp(player1Name, white) == 0 &&
4599             strcmp(player2Name, black) == 0) {
4600             if (appData.debugMode)
4601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4602                       player1Rating, player2Rating);
4603             gameInfo.whiteRating = player1Rating;
4604             gameInfo.blackRating = player2Rating;
4605         } else if (strcmp(player2Name, white) == 0 &&
4606                    strcmp(player1Name, black) == 0) {
4607             if (appData.debugMode)
4608               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4609                       player2Rating, player1Rating);
4610             gameInfo.whiteRating = player2Rating;
4611             gameInfo.blackRating = player1Rating;
4612         }
4613         player1Name[0] = player2Name[0] = NULLCHAR;
4614
4615         /* Silence shouts if requested */
4616         if (appData.quietPlay &&
4617             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4618             SendToICS(ics_prefix);
4619             SendToICS("set shout 0\n");
4620         }
4621     }
4622
4623     /* Deal with midgame name changes */
4624     if (!newGame) {
4625         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4626             if (gameInfo.white) free(gameInfo.white);
4627             gameInfo.white = StrSave(white);
4628         }
4629         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4630             if (gameInfo.black) free(gameInfo.black);
4631             gameInfo.black = StrSave(black);
4632         }
4633     }
4634
4635     /* Throw away game result if anything actually changes in examine mode */
4636     if (gameMode == IcsExamining && !newGame) {
4637         gameInfo.result = GameUnfinished;
4638         if (gameInfo.resultDetails != NULL) {
4639             free(gameInfo.resultDetails);
4640             gameInfo.resultDetails = NULL;
4641         }
4642     }
4643
4644     /* In pausing && IcsExamining mode, we ignore boards coming
4645        in if they are in a different variation than we are. */
4646     if (pauseExamInvalid) return;
4647     if (pausing && gameMode == IcsExamining) {
4648         if (moveNum <= pauseExamForwardMostMove) {
4649             pauseExamInvalid = TRUE;
4650             forwardMostMove = pauseExamForwardMostMove;
4651             return;
4652         }
4653     }
4654
4655   if (appData.debugMode) {
4656     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4657   }
4658     /* Parse the board */
4659     for (k = 0; k < ranks; k++) {
4660       for (j = 0; j < files; j++)
4661         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4662       if(gameInfo.holdingsWidth > 1) {
4663            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4664            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4665       }
4666     }
4667     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4668       board[5][BOARD_RGHT+1] = WhiteAngel;
4669       board[6][BOARD_RGHT+1] = WhiteMarshall;
4670       board[1][0] = BlackMarshall;
4671       board[2][0] = BlackAngel;
4672       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4673     }
4674     CopyBoard(boards[moveNum], board);
4675     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4676     if (moveNum == 0) {
4677         startedFromSetupPosition =
4678           !CompareBoards(board, initialPosition);
4679         if(startedFromSetupPosition)
4680             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4681     }
4682
4683     /* [HGM] Set castling rights. Take the outermost Rooks,
4684        to make it also work for FRC opening positions. Note that board12
4685        is really defective for later FRC positions, as it has no way to
4686        indicate which Rook can castle if they are on the same side of King.
4687        For the initial position we grant rights to the outermost Rooks,
4688        and remember thos rights, and we then copy them on positions
4689        later in an FRC game. This means WB might not recognize castlings with
4690        Rooks that have moved back to their original position as illegal,
4691        but in ICS mode that is not its job anyway.
4692     */
4693     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4694     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4695
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[0][i] == WhiteRook) j = i;
4698         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[0][i] == WhiteRook) j = i;
4701         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4704         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4707         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708
4709         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4710         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4712             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714             if(board[BOARD_HEIGHT-1][k] == bKing)
4715                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4716         if(gameInfo.variant == VariantTwoKings) {
4717             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4718             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4719             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4720         }
4721     } else { int r;
4722         r = boards[moveNum][CASTLING][0] = initialRights[0];
4723         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4724         r = boards[moveNum][CASTLING][1] = initialRights[1];
4725         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4726         r = boards[moveNum][CASTLING][3] = initialRights[3];
4727         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4728         r = boards[moveNum][CASTLING][4] = initialRights[4];
4729         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4730         /* wildcastle kludge: always assume King has rights */
4731         r = boards[moveNum][CASTLING][2] = initialRights[2];
4732         r = boards[moveNum][CASTLING][5] = initialRights[5];
4733     }
4734     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4735     boards[moveNum][EP_STATUS] = EP_NONE;
4736     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4737     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4738     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4739
4740
4741     if (ics_getting_history == H_GOT_REQ_HEADER ||
4742         ics_getting_history == H_GOT_UNREQ_HEADER) {
4743         /* This was an initial position from a move list, not
4744            the current position */
4745         return;
4746     }
4747
4748     /* Update currentMove and known move number limits */
4749     newMove = newGame || moveNum > forwardMostMove;
4750
4751     if (newGame) {
4752         forwardMostMove = backwardMostMove = currentMove = moveNum;
4753         if (gameMode == IcsExamining && moveNum == 0) {
4754           /* Workaround for ICS limitation: we are not told the wild
4755              type when starting to examine a game.  But if we ask for
4756              the move list, the move list header will tell us */
4757             ics_getting_history = H_REQUESTED;
4758             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4759             SendToICS(str);
4760         }
4761     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4762                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4763 #if ZIPPY
4764         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4765         /* [HGM] applied this also to an engine that is silently watching        */
4766         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4767             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4768             gameInfo.variant == currentlyInitializedVariant) {
4769           takeback = forwardMostMove - moveNum;
4770           for (i = 0; i < takeback; i++) {
4771             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4772             SendToProgram("undo\n", &first);
4773           }
4774         }
4775 #endif
4776
4777         forwardMostMove = moveNum;
4778         if (!pausing || currentMove > forwardMostMove)
4779           currentMove = forwardMostMove;
4780     } else {
4781         /* New part of history that is not contiguous with old part */
4782         if (pausing && gameMode == IcsExamining) {
4783             pauseExamInvalid = TRUE;
4784             forwardMostMove = pauseExamForwardMostMove;
4785             return;
4786         }
4787         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4788 #if ZIPPY
4789             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4790                 // [HGM] when we will receive the move list we now request, it will be
4791                 // fed to the engine from the first move on. So if the engine is not
4792                 // in the initial position now, bring it there.
4793                 InitChessProgram(&first, 0);
4794             }
4795 #endif
4796             ics_getting_history = H_REQUESTED;
4797             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4798             SendToICS(str);
4799         }
4800         forwardMostMove = backwardMostMove = currentMove = moveNum;
4801     }
4802
4803     /* Update the clocks */
4804     if (strchr(elapsed_time, '.')) {
4805       /* Time is in ms */
4806       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4807       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4808     } else {
4809       /* Time is in seconds */
4810       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4811       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4812     }
4813
4814
4815 #if ZIPPY
4816     if (appData.zippyPlay && newGame &&
4817         gameMode != IcsObserving && gameMode != IcsIdle &&
4818         gameMode != IcsExamining)
4819       ZippyFirstBoard(moveNum, basetime, increment);
4820 #endif
4821
4822     /* Put the move on the move list, first converting
4823        to canonical algebraic form. */
4824     if (moveNum > 0) {
4825   if (appData.debugMode) {
4826     int f = forwardMostMove;
4827     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4828             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4829             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4830     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4831     fprintf(debugFP, "moveNum = %d\n", moveNum);
4832     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4833     setbuf(debugFP, NULL);
4834   }
4835         if (moveNum <= backwardMostMove) {
4836             /* We don't know what the board looked like before
4837                this move.  Punt. */
4838           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4839             strcat(parseList[moveNum - 1], " ");
4840             strcat(parseList[moveNum - 1], elapsed_time);
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842         } else if (strcmp(move_str, "none") == 0) {
4843             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4844             /* Again, we don't know what the board looked like;
4845                this is really the start of the game. */
4846             parseList[moveNum - 1][0] = NULLCHAR;
4847             moveList[moveNum - 1][0] = NULLCHAR;
4848             backwardMostMove = moveNum;
4849             startedFromSetupPosition = TRUE;
4850             fromX = fromY = toX = toY = -1;
4851         } else {
4852           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4853           //                 So we parse the long-algebraic move string in stead of the SAN move
4854           int valid; char buf[MSG_SIZ], *prom;
4855
4856           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4857                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4858           // str looks something like "Q/a1-a2"; kill the slash
4859           if(str[1] == '/')
4860             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4861           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4862           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4863                 strcat(buf, prom); // long move lacks promo specification!
4864           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4865                 if(appData.debugMode)
4866                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4867                 safeStrCpy(move_str, buf, MSG_SIZ);
4868           }
4869           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4870                                 &fromX, &fromY, &toX, &toY, &promoChar)
4871                || ParseOneMove(buf, moveNum - 1, &moveType,
4872                                 &fromX, &fromY, &toX, &toY, &promoChar);
4873           // end of long SAN patch
4874           if (valid) {
4875             (void) CoordsToAlgebraic(boards[moveNum - 1],
4876                                      PosFlags(moveNum - 1),
4877                                      fromY, fromX, toY, toX, promoChar,
4878                                      parseList[moveNum-1]);
4879             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4880               case MT_NONE:
4881               case MT_STALEMATE:
4882               default:
4883                 break;
4884               case MT_CHECK:
4885                 if(!IS_SHOGI(gameInfo.variant))
4886                     strcat(parseList[moveNum - 1], "+");
4887                 break;
4888               case MT_CHECKMATE:
4889               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4890                 strcat(parseList[moveNum - 1], "#");
4891                 break;
4892             }
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             /* currentMoveString is set as a side-effect of ParseOneMove */
4896             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4897             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4898             strcat(moveList[moveNum - 1], "\n");
4899
4900             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4901                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4902               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4903                 ChessSquare old, new = boards[moveNum][k][j];
4904                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4905                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4906                   if(old == new) continue;
4907                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4908                   else if(new == WhiteWazir || new == BlackWazir) {
4909                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4910                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4911                       else boards[moveNum][k][j] = old; // preserve type of Gold
4912                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4913                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4914               }
4915           } else {
4916             /* Move from ICS was illegal!?  Punt. */
4917             if (appData.debugMode) {
4918               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4919               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4920             }
4921             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4922             strcat(parseList[moveNum - 1], " ");
4923             strcat(parseList[moveNum - 1], elapsed_time);
4924             moveList[moveNum - 1][0] = NULLCHAR;
4925             fromX = fromY = toX = toY = -1;
4926           }
4927         }
4928   if (appData.debugMode) {
4929     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4930     setbuf(debugFP, NULL);
4931   }
4932
4933 #if ZIPPY
4934         /* Send move to chess program (BEFORE animating it). */
4935         if (appData.zippyPlay && !newGame && newMove &&
4936            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4937
4938             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4939                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4940                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4941                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4942                             move_str);
4943                     DisplayError(str, 0);
4944                 } else {
4945                     if (first.sendTime) {
4946                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4947                     }
4948                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4949                     if (firstMove && !bookHit) {
4950                         firstMove = FALSE;
4951                         if (first.useColors) {
4952                           SendToProgram(gameMode == IcsPlayingWhite ?
4953                                         "white\ngo\n" :
4954                                         "black\ngo\n", &first);
4955                         } else {
4956                           SendToProgram("go\n", &first);
4957                         }
4958                         first.maybeThinking = TRUE;
4959                     }
4960                 }
4961             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4962               if (moveList[moveNum - 1][0] == NULLCHAR) {
4963                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4964                 DisplayError(str, 0);
4965               } else {
4966                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4967                 SendMoveToProgram(moveNum - 1, &first);
4968               }
4969             }
4970         }
4971 #endif
4972     }
4973
4974     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4975         /* If move comes from a remote source, animate it.  If it
4976            isn't remote, it will have already been animated. */
4977         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4978             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4979         }
4980         if (!pausing && appData.highlightLastMove) {
4981             SetHighlights(fromX, fromY, toX, toY);
4982         }
4983     }
4984
4985     /* Start the clocks */
4986     whiteFlag = blackFlag = FALSE;
4987     appData.clockMode = !(basetime == 0 && increment == 0);
4988     if (ticking == 0) {
4989       ics_clock_paused = TRUE;
4990       StopClocks();
4991     } else if (ticking == 1) {
4992       ics_clock_paused = FALSE;
4993     }
4994     if (gameMode == IcsIdle ||
4995         relation == RELATION_OBSERVING_STATIC ||
4996         relation == RELATION_EXAMINING ||
4997         ics_clock_paused)
4998       DisplayBothClocks();
4999     else
5000       StartClocks();
5001
5002     /* Display opponents and material strengths */
5003     if (gameInfo.variant != VariantBughouse &&
5004         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5005         if (tinyLayout || smallLayout) {
5006             if(gameInfo.variant == VariantNormal)
5007               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5008                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5009                     basetime, increment);
5010             else
5011               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5012                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5013                     basetime, increment, (int) gameInfo.variant);
5014         } else {
5015             if(gameInfo.variant == VariantNormal)
5016               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5017                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5018                     basetime, increment);
5019             else
5020               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5021                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5022                     basetime, increment, VariantName(gameInfo.variant));
5023         }
5024         DisplayTitle(str);
5025   if (appData.debugMode) {
5026     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5027   }
5028     }
5029
5030
5031     /* Display the board */
5032     if (!pausing && !appData.noGUI) {
5033
5034       if (appData.premove)
5035           if (!gotPremove ||
5036              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5037              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5038               ClearPremoveHighlights();
5039
5040       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5041         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5042       DrawPosition(j, boards[currentMove]);
5043
5044       DisplayMove(moveNum - 1);
5045       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5046             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5047               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5048         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5049       }
5050     }
5051
5052     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5053 #if ZIPPY
5054     if(bookHit) { // [HGM] book: simulate book reply
5055         static char bookMove[MSG_SIZ]; // a bit generous?
5056
5057         programStats.nodes = programStats.depth = programStats.time =
5058         programStats.score = programStats.got_only_move = 0;
5059         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5060
5061         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5062         strcat(bookMove, bookHit);
5063         HandleMachineMove(bookMove, &first);
5064     }
5065 #endif
5066 }
5067
5068 void
5069 GetMoveListEvent ()
5070 {
5071     char buf[MSG_SIZ];
5072     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5073         ics_getting_history = H_REQUESTED;
5074         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5075         SendToICS(buf);
5076     }
5077 }
5078
5079 void
5080 SendToBoth (char *msg)
5081 {   // to make it easy to keep two engines in step in dual analysis
5082     SendToProgram(msg, &first);
5083     if(second.analyzing) SendToProgram(msg, &second);
5084 }
5085
5086 void
5087 AnalysisPeriodicEvent (int force)
5088 {
5089     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5090          && !force) || !appData.periodicUpdates)
5091       return;
5092
5093     /* Send . command to Crafty to collect stats */
5094     SendToBoth(".\n");
5095
5096     /* Don't send another until we get a response (this makes
5097        us stop sending to old Crafty's which don't understand
5098        the "." command (sending illegal cmds resets node count & time,
5099        which looks bad)) */
5100     programStats.ok_to_send = 0;
5101 }
5102
5103 void
5104 ics_update_width (int new_width)
5105 {
5106         ics_printf("set width %d\n", new_width);
5107 }
5108
5109 void
5110 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5111 {
5112     char buf[MSG_SIZ];
5113
5114     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5115         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5116             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5117             SendToProgram(buf, cps);
5118             return;
5119         }
5120         // null move in variant where engine does not understand it (for analysis purposes)
5121         SendBoard(cps, moveNum + 1); // send position after move in stead.
5122         return;
5123     }
5124     if (cps->useUsermove) {
5125       SendToProgram("usermove ", cps);
5126     }
5127     if (cps->useSAN) {
5128       char *space;
5129       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5130         int len = space - parseList[moveNum];
5131         memcpy(buf, parseList[moveNum], len);
5132         buf[len++] = '\n';
5133         buf[len] = NULLCHAR;
5134       } else {
5135         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5136       }
5137       SendToProgram(buf, cps);
5138     } else {
5139       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5140         AlphaRank(moveList[moveNum], 4);
5141         SendToProgram(moveList[moveNum], cps);
5142         AlphaRank(moveList[moveNum], 4); // and back
5143       } else
5144       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5145        * the engine. It would be nice to have a better way to identify castle
5146        * moves here. */
5147       if(appData.fischerCastling && cps->useOOCastle) {
5148         int fromX = moveList[moveNum][0] - AAA;
5149         int fromY = moveList[moveNum][1] - ONE;
5150         int toX = moveList[moveNum][2] - AAA;
5151         int toY = moveList[moveNum][3] - ONE;
5152         if((boards[moveNum][fromY][fromX] == WhiteKing
5153             && boards[moveNum][toY][toX] == WhiteRook)
5154            || (boards[moveNum][fromY][fromX] == BlackKing
5155                && boards[moveNum][toY][toX] == BlackRook)) {
5156           if(toX > fromX) SendToProgram("O-O\n", cps);
5157           else SendToProgram("O-O-O\n", cps);
5158         }
5159         else SendToProgram(moveList[moveNum], cps);
5160       } else
5161       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5162         char *m = moveList[moveNum];
5163         static char c[2];
5164         *c = m[7]; // promoChar
5165         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5167                                                m[2], m[3] - '0',
5168                                                m[5], m[6] - '0',
5169                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5170         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5171           *c = m[9];
5172           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5173                                                m[7], m[8] - '0',
5174                                                m[7], m[8] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[5], m[6] - '0',
5177                                                m[2], m[3] - '0', c);
5178         } else
5179           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5180                                                m[5], m[6] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[2], m[3] - '0', c);
5183           SendToProgram(buf, cps);
5184       } else
5185       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5186         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5187           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5188           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5189                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5190         } else
5191           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5192                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5193         SendToProgram(buf, cps);
5194       }
5195       else SendToProgram(moveList[moveNum], cps);
5196       /* End of additions by Tord */
5197     }
5198
5199     /* [HGM] setting up the opening has brought engine in force mode! */
5200     /*       Send 'go' if we are in a mode where machine should play. */
5201     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5202         (gameMode == TwoMachinesPlay   ||
5203 #if ZIPPY
5204          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5205 #endif
5206          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5207         SendToProgram("go\n", cps);
5208   if (appData.debugMode) {
5209     fprintf(debugFP, "(extra)\n");
5210   }
5211     }
5212     setboardSpoiledMachineBlack = 0;
5213 }
5214
5215 void
5216 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5217 {
5218     char user_move[MSG_SIZ];
5219     char suffix[4];
5220
5221     if(gameInfo.variant == VariantSChess && promoChar) {
5222         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5223         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5224     } else suffix[0] = NULLCHAR;
5225
5226     switch (moveType) {
5227       default:
5228         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5229                 (int)moveType, fromX, fromY, toX, toY);
5230         DisplayError(user_move + strlen("say "), 0);
5231         break;
5232       case WhiteKingSideCastle:
5233       case BlackKingSideCastle:
5234       case WhiteQueenSideCastleWild:
5235       case BlackQueenSideCastleWild:
5236       /* PUSH Fabien */
5237       case WhiteHSideCastleFR:
5238       case BlackHSideCastleFR:
5239       /* POP Fabien */
5240         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5241         break;
5242       case WhiteQueenSideCastle:
5243       case BlackQueenSideCastle:
5244       case WhiteKingSideCastleWild:
5245       case BlackKingSideCastleWild:
5246       /* PUSH Fabien */
5247       case WhiteASideCastleFR:
5248       case BlackASideCastleFR:
5249       /* POP Fabien */
5250         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5251         break;
5252       case WhiteNonPromotion:
5253       case BlackNonPromotion:
5254         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5255         break;
5256       case WhitePromotion:
5257       case BlackPromotion:
5258         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5259            gameInfo.variant == VariantMakruk)
5260           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5262                 PieceToChar(WhiteFerz));
5263         else if(gameInfo.variant == VariantGreat)
5264           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5265                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5266                 PieceToChar(WhiteMan));
5267         else
5268           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5269                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5270                 promoChar);
5271         break;
5272       case WhiteDrop:
5273       case BlackDrop:
5274       drop:
5275         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5276                  ToUpper(PieceToChar((ChessSquare) fromX)),
5277                  AAA + toX, ONE + toY);
5278         break;
5279       case IllegalMove:  /* could be a variant we don't quite understand */
5280         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5281       case NormalMove:
5282       case WhiteCapturesEnPassant:
5283       case BlackCapturesEnPassant:
5284         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5285                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5286         break;
5287     }
5288     SendToICS(user_move);
5289     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5290         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5291 }
5292
5293 void
5294 UploadGameEvent ()
5295 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5296     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5297     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5298     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5299       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5300       return;
5301     }
5302     if(gameMode != IcsExamining) { // is this ever not the case?
5303         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5304
5305         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5306           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5307         } else { // on FICS we must first go to general examine mode
5308           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5309         }
5310         if(gameInfo.variant != VariantNormal) {
5311             // try figure out wild number, as xboard names are not always valid on ICS
5312             for(i=1; i<=36; i++) {
5313               snprintf(buf, MSG_SIZ, "wild/%d", i);
5314                 if(StringToVariant(buf) == gameInfo.variant) break;
5315             }
5316             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5317             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5318             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5319         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5320         SendToICS(ics_prefix);
5321         SendToICS(buf);
5322         if(startedFromSetupPosition || backwardMostMove != 0) {
5323           fen = PositionToFEN(backwardMostMove, NULL, 1);
5324           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5325             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5326             SendToICS(buf);
5327           } else { // FICS: everything has to set by separate bsetup commands
5328             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5329             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5330             SendToICS(buf);
5331             if(!WhiteOnMove(backwardMostMove)) {
5332                 SendToICS("bsetup tomove black\n");
5333             }
5334             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5335             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5336             SendToICS(buf);
5337             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5338             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5339             SendToICS(buf);
5340             i = boards[backwardMostMove][EP_STATUS];
5341             if(i >= 0) { // set e.p.
5342               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5343                 SendToICS(buf);
5344             }
5345             bsetup++;
5346           }
5347         }
5348       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5349             SendToICS("bsetup done\n"); // switch to normal examining.
5350     }
5351     for(i = backwardMostMove; i<last; i++) {
5352         char buf[20];
5353         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5354         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5355             int len = strlen(moveList[i]);
5356             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5357             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5358         }
5359         SendToICS(buf);
5360     }
5361     SendToICS(ics_prefix);
5362     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5363 }
5364
5365 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5366 int legNr = 1;
5367
5368 void
5369 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5370 {
5371     if (rf == DROP_RANK) {
5372       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5373       sprintf(move, "%c@%c%c\n",
5374                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5375     } else {
5376         if (promoChar == 'x' || promoChar == NULLCHAR) {
5377           sprintf(move, "%c%c%c%c\n",
5378                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5379           if(killX >= 0 && killY >= 0) {
5380             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5381             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5382           }
5383         } else {
5384             sprintf(move, "%c%c%c%c%c\n",
5385                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5386           if(killX >= 0 && killY >= 0) {
5387             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5388             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5389           }
5390         }
5391     }
5392 }
5393
5394 void
5395 ProcessICSInitScript (FILE *f)
5396 {
5397     char buf[MSG_SIZ];
5398
5399     while (fgets(buf, MSG_SIZ, f)) {
5400         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5401     }
5402
5403     fclose(f);
5404 }
5405
5406
5407 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5408 int dragging;
5409 static ClickType lastClickType;
5410
5411 int
5412 PieceInString (char *s, ChessSquare piece)
5413 {
5414   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5415   while((p = strchr(s, ID))) {
5416     if(!suffix || p[1] == suffix) return TRUE;
5417     s = p;
5418   }
5419   return FALSE;
5420 }
5421
5422 int
5423 Partner (ChessSquare *p)
5424 { // change piece into promotion partner if one shogi-promotes to the other
5425   ChessSquare partner = promoPartner[*p];
5426   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5427   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5428   *p = partner;
5429   return 1;
5430 }
5431
5432 void
5433 Sweep (int step)
5434 {
5435     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5436     static int toggleFlag;
5437     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5438     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5439     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5440     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5441     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5442     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5443     do {
5444         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5445         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5446         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5447         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5448         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5449         if(!step) step = -1;
5450     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5451             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5452             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5453             promoSweep == pawn ||
5454             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5455             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5456     if(toX >= 0) {
5457         int victim = boards[currentMove][toY][toX];
5458         boards[currentMove][toY][toX] = promoSweep;
5459         DrawPosition(FALSE, boards[currentMove]);
5460         boards[currentMove][toY][toX] = victim;
5461     } else
5462     ChangeDragPiece(promoSweep);
5463 }
5464
5465 int
5466 PromoScroll (int x, int y)
5467 {
5468   int step = 0;
5469
5470   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5471   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5472   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5473   if(!step) return FALSE;
5474   lastX = x; lastY = y;
5475   if((promoSweep < BlackPawn) == flipView) step = -step;
5476   if(step > 0) selectFlag = 1;
5477   if(!selectFlag) Sweep(step);
5478   return FALSE;
5479 }
5480
5481 void
5482 NextPiece (int step)
5483 {
5484     ChessSquare piece = boards[currentMove][toY][toX];
5485     do {
5486         pieceSweep -= step;
5487         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5488         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5489         if(!step) step = -1;
5490     } while(PieceToChar(pieceSweep) == '.');
5491     boards[currentMove][toY][toX] = pieceSweep;
5492     DrawPosition(FALSE, boards[currentMove]);
5493     boards[currentMove][toY][toX] = piece;
5494 }
5495 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5496 void
5497 AlphaRank (char *move, int n)
5498 {
5499 //    char *p = move, c; int x, y;
5500
5501     if (appData.debugMode) {
5502         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5503     }
5504
5505     if(move[1]=='*' &&
5506        move[2]>='0' && move[2]<='9' &&
5507        move[3]>='a' && move[3]<='x'    ) {
5508         move[1] = '@';
5509         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5510         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5511     } else
5512     if(move[0]>='0' && move[0]<='9' &&
5513        move[1]>='a' && move[1]<='x' &&
5514        move[2]>='0' && move[2]<='9' &&
5515        move[3]>='a' && move[3]<='x'    ) {
5516         /* input move, Shogi -> normal */
5517         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5518         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5519         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5520         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5521     } else
5522     if(move[1]=='@' &&
5523        move[3]>='0' && move[3]<='9' &&
5524        move[2]>='a' && move[2]<='x'    ) {
5525         move[1] = '*';
5526         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5527         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5528     } else
5529     if(
5530        move[0]>='a' && move[0]<='x' &&
5531        move[3]>='0' && move[3]<='9' &&
5532        move[2]>='a' && move[2]<='x'    ) {
5533          /* output move, normal -> Shogi */
5534         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5535         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5536         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5537         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5538         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5539     }
5540     if (appData.debugMode) {
5541         fprintf(debugFP, "   out = '%s'\n", move);
5542     }
5543 }
5544
5545 char yy_textstr[8000];
5546
5547 /* Parser for moves from gnuchess, ICS, or user typein box */
5548 Boolean
5549 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5550 {
5551     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5552
5553     switch (*moveType) {
5554       case WhitePromotion:
5555       case BlackPromotion:
5556       case WhiteNonPromotion:
5557       case BlackNonPromotion:
5558       case NormalMove:
5559       case FirstLeg:
5560       case WhiteCapturesEnPassant:
5561       case BlackCapturesEnPassant:
5562       case WhiteKingSideCastle:
5563       case WhiteQueenSideCastle:
5564       case BlackKingSideCastle:
5565       case BlackQueenSideCastle:
5566       case WhiteKingSideCastleWild:
5567       case WhiteQueenSideCastleWild:
5568       case BlackKingSideCastleWild:
5569       case BlackQueenSideCastleWild:
5570       /* Code added by Tord: */
5571       case WhiteHSideCastleFR:
5572       case WhiteASideCastleFR:
5573       case BlackHSideCastleFR:
5574       case BlackASideCastleFR:
5575       /* End of code added by Tord */
5576       case IllegalMove:         /* bug or odd chess variant */
5577         if(currentMoveString[1] == '@') { // illegal drop
5578           *fromX = WhiteOnMove(moveNum) ?
5579             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5580             (int) CharToPiece(ToLower(currentMoveString[0]));
5581           goto drop;
5582         }
5583         *fromX = currentMoveString[0] - AAA;
5584         *fromY = currentMoveString[1] - ONE;
5585         *toX = currentMoveString[2] - AAA;
5586         *toY = currentMoveString[3] - ONE;
5587         *promoChar = currentMoveString[4];
5588         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5589         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5590             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5591     if (appData.debugMode) {
5592         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5593     }
5594             *fromX = *fromY = *toX = *toY = 0;
5595             return FALSE;
5596         }
5597         if (appData.testLegality) {
5598           return (*moveType != IllegalMove);
5599         } else {
5600           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5601                          // [HGM] lion: if this is a double move we are less critical
5602                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5603         }
5604
5605       case WhiteDrop:
5606       case BlackDrop:
5607         *fromX = *moveType == WhiteDrop ?
5608           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5609           (int) CharToPiece(ToLower(currentMoveString[0]));
5610       drop:
5611         *fromY = DROP_RANK;
5612         *toX = currentMoveString[2] - AAA;
5613         *toY = currentMoveString[3] - ONE;
5614         *promoChar = NULLCHAR;
5615         return TRUE;
5616
5617       case AmbiguousMove:
5618       case ImpossibleMove:
5619       case EndOfFile:
5620       case ElapsedTime:
5621       case Comment:
5622       case PGNTag:
5623       case NAG:
5624       case WhiteWins:
5625       case BlackWins:
5626       case GameIsDrawn:
5627       default:
5628     if (appData.debugMode) {
5629         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5630     }
5631         /* bug? */
5632         *fromX = *fromY = *toX = *toY = 0;
5633         *promoChar = NULLCHAR;
5634         return FALSE;
5635     }
5636 }
5637
5638 Boolean pushed = FALSE;
5639 char *lastParseAttempt;
5640
5641 void
5642 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5643 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5644   int fromX, fromY, toX, toY; char promoChar;
5645   ChessMove moveType;
5646   Boolean valid;
5647   int nr = 0;
5648
5649   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5650   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5651     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5652     pushed = TRUE;
5653   }
5654   endPV = forwardMostMove;
5655   do {
5656     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5657     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5658     lastParseAttempt = pv;
5659     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5660     if(!valid && nr == 0 &&
5661        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5662         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5663         // Hande case where played move is different from leading PV move
5664         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5665         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5666         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5667         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5668           endPV += 2; // if position different, keep this
5669           moveList[endPV-1][0] = fromX + AAA;
5670           moveList[endPV-1][1] = fromY + ONE;
5671           moveList[endPV-1][2] = toX + AAA;
5672           moveList[endPV-1][3] = toY + ONE;
5673           parseList[endPV-1][0] = NULLCHAR;
5674           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5675         }
5676       }
5677     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5678     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5679     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5680     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5681         valid++; // allow comments in PV
5682         continue;
5683     }
5684     nr++;
5685     if(endPV+1 > framePtr) break; // no space, truncate
5686     if(!valid) break;
5687     endPV++;
5688     CopyBoard(boards[endPV], boards[endPV-1]);
5689     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5690     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5691     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5692     CoordsToAlgebraic(boards[endPV - 1],
5693                              PosFlags(endPV - 1),
5694                              fromY, fromX, toY, toX, promoChar,
5695                              parseList[endPV - 1]);
5696   } while(valid);
5697   if(atEnd == 2) return; // used hidden, for PV conversion
5698   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5699   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5700   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5701                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5702   DrawPosition(TRUE, boards[currentMove]);
5703 }
5704
5705 int
5706 MultiPV (ChessProgramState *cps, int kind)
5707 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5708         int i;
5709         for(i=0; i<cps->nrOptions; i++) {
5710             char *s = cps->option[i].name;
5711             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5712             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5713                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5714         }
5715         return -1;
5716 }
5717
5718 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5719 static int multi, pv_margin;
5720 static ChessProgramState *activeCps;
5721
5722 Boolean
5723 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5724 {
5725         int startPV, lineStart, origIndex = index;
5726         char *p, buf2[MSG_SIZ];
5727         ChessProgramState *cps = (pane ? &second : &first);
5728
5729         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5730         lastX = x; lastY = y;
5731         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5732         lineStart = startPV = index;
5733         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5734         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5735         index = startPV;
5736         do{ while(buf[index] && buf[index] != '\n') index++;
5737         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5738         buf[index] = 0;
5739         if(lineStart == 0 && gameMode == AnalyzeMode) {
5740             int n = 0;
5741             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5742             if(n == 0) { // click not on "fewer" or "more"
5743                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5744                     pv_margin = cps->option[multi].value;
5745                     activeCps = cps; // non-null signals margin adjustment
5746                 }
5747             } else if((multi = MultiPV(cps, 1)) >= 0) {
5748                 n += cps->option[multi].value; if(n < 1) n = 1;
5749                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5750                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5751                 cps->option[multi].value = n;
5752                 *start = *end = 0;
5753                 return FALSE;
5754             }
5755         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5756                 ExcludeClick(origIndex - lineStart);
5757                 return FALSE;
5758         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5759                 Collapse(origIndex - lineStart);
5760                 return FALSE;
5761         }
5762         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5763         *start = startPV; *end = index-1;
5764         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5765         return TRUE;
5766 }
5767
5768 char *
5769 PvToSAN (char *pv)
5770 {
5771         static char buf[10*MSG_SIZ];
5772         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5773         *buf = NULLCHAR;
5774         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5775         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5776         for(i = forwardMostMove; i<endPV; i++){
5777             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5778             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5779             k += strlen(buf+k);
5780         }
5781         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5782         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5783         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5784         endPV = savedEnd;
5785         return buf;
5786 }
5787
5788 Boolean
5789 LoadPV (int x, int y)
5790 { // called on right mouse click to load PV
5791   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5792   lastX = x; lastY = y;
5793   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5794   extendGame = FALSE;
5795   return TRUE;
5796 }
5797
5798 void
5799 UnLoadPV ()
5800 {
5801   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5802   if(activeCps) {
5803     if(pv_margin != activeCps->option[multi].value) {
5804       char buf[MSG_SIZ];
5805       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5806       SendToProgram(buf, activeCps);
5807       activeCps->option[multi].value = pv_margin;
5808     }
5809     activeCps = NULL;
5810     return;
5811   }
5812   if(endPV < 0) return;
5813   if(appData.autoCopyPV) CopyFENToClipboard();
5814   endPV = -1;
5815   if(extendGame && currentMove > forwardMostMove) {
5816         Boolean saveAnimate = appData.animate;
5817         if(pushed) {
5818             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5819                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5820             } else storedGames--; // abandon shelved tail of original game
5821         }
5822         pushed = FALSE;
5823         forwardMostMove = currentMove;
5824         currentMove = oldFMM;
5825         appData.animate = FALSE;
5826         ToNrEvent(forwardMostMove);
5827         appData.animate = saveAnimate;
5828   }
5829   currentMove = forwardMostMove;
5830   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5831   ClearPremoveHighlights();
5832   DrawPosition(TRUE, boards[currentMove]);
5833 }
5834
5835 void
5836 MovePV (int x, int y, int h)
5837 { // step through PV based on mouse coordinates (called on mouse move)
5838   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5839
5840   if(activeCps) { // adjusting engine's multi-pv margin
5841     if(x > lastX) pv_margin++; else
5842     if(x < lastX) pv_margin -= (pv_margin > 0);
5843     if(x != lastX) {
5844       char buf[MSG_SIZ];
5845       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5846       DisplayMessage(buf, "");
5847     }
5848     lastX = x;
5849     return;
5850   }
5851   // we must somehow check if right button is still down (might be released off board!)
5852   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5853   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5854   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5855   if(!step) return;
5856   lastX = x; lastY = y;
5857
5858   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5859   if(endPV < 0) return;
5860   if(y < margin) step = 1; else
5861   if(y > h - margin) step = -1;
5862   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5863   currentMove += step;
5864   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5865   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5866                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5867   DrawPosition(FALSE, boards[currentMove]);
5868 }
5869
5870
5871 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5872 // All positions will have equal probability, but the current method will not provide a unique
5873 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5874 #define DARK 1
5875 #define LITE 2
5876 #define ANY 3
5877
5878 int squaresLeft[4];
5879 int piecesLeft[(int)BlackPawn];
5880 int seed, nrOfShuffles;
5881
5882 void
5883 GetPositionNumber ()
5884 {       // sets global variable seed
5885         int i;
5886
5887         seed = appData.defaultFrcPosition;
5888         if(seed < 0) { // randomize based on time for negative FRC position numbers
5889                 for(i=0; i<50; i++) seed += random();
5890                 seed = random() ^ random() >> 8 ^ random() << 8;
5891                 if(seed<0) seed = -seed;
5892         }
5893 }
5894
5895 int
5896 put (Board board, int pieceType, int rank, int n, int shade)
5897 // put the piece on the (n-1)-th empty squares of the given shade
5898 {
5899         int i;
5900
5901         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5902                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5903                         board[rank][i] = (ChessSquare) pieceType;
5904                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5905                         squaresLeft[ANY]--;
5906                         piecesLeft[pieceType]--;
5907                         return i;
5908                 }
5909         }
5910         return -1;
5911 }
5912
5913
5914 void
5915 AddOnePiece (Board board, int pieceType, int rank, int shade)
5916 // calculate where the next piece goes, (any empty square), and put it there
5917 {
5918         int i;
5919
5920         i = seed % squaresLeft[shade];
5921         nrOfShuffles *= squaresLeft[shade];
5922         seed /= squaresLeft[shade];
5923         put(board, pieceType, rank, i, shade);
5924 }
5925
5926 void
5927 AddTwoPieces (Board board, int pieceType, int rank)
5928 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5929 {
5930         int i, n=squaresLeft[ANY], j=n-1, k;
5931
5932         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5933         i = seed % k;  // pick one
5934         nrOfShuffles *= k;
5935         seed /= k;
5936         while(i >= j) i -= j--;
5937         j = n - 1 - j; i += j;
5938         put(board, pieceType, rank, j, ANY);
5939         put(board, pieceType, rank, i, ANY);
5940 }
5941
5942 void
5943 SetUpShuffle (Board board, int number)
5944 {
5945         int i, p, first=1;
5946
5947         GetPositionNumber(); nrOfShuffles = 1;
5948
5949         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5950         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5951         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5952
5953         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5954
5955         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5956             p = (int) board[0][i];
5957             if(p < (int) BlackPawn) piecesLeft[p] ++;
5958             board[0][i] = EmptySquare;
5959         }
5960
5961         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5962             // shuffles restricted to allow normal castling put KRR first
5963             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5964                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5965             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5966                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5967             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5968                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5969             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5970                 put(board, WhiteRook, 0, 0, ANY);
5971             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5972         }
5973
5974         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5975             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5976             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5977                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5978                 while(piecesLeft[p] >= 2) {
5979                     AddOnePiece(board, p, 0, LITE);
5980                     AddOnePiece(board, p, 0, DARK);
5981                 }
5982                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5983             }
5984
5985         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5986             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5987             // but we leave King and Rooks for last, to possibly obey FRC restriction
5988             if(p == (int)WhiteRook) continue;
5989             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5990             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5991         }
5992
5993         // now everything is placed, except perhaps King (Unicorn) and Rooks
5994
5995         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5996             // Last King gets castling rights
5997             while(piecesLeft[(int)WhiteUnicorn]) {
5998                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5999                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6000             }
6001
6002             while(piecesLeft[(int)WhiteKing]) {
6003                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6004                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6005             }
6006
6007
6008         } else {
6009             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6010             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6011         }
6012
6013         // Only Rooks can be left; simply place them all
6014         while(piecesLeft[(int)WhiteRook]) {
6015                 i = put(board, WhiteRook, 0, 0, ANY);
6016                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6017                         if(first) {
6018                                 first=0;
6019                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6020                         }
6021                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6022                 }
6023         }
6024         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6025             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6026         }
6027
6028         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6029 }
6030
6031 int
6032 ptclen (const char *s, char *escapes)
6033 {
6034     int n = 0;
6035     if(!*escapes) return strlen(s);
6036     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6037     return n;
6038 }
6039
6040 int
6041 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6042 /* [HGM] moved here from winboard.c because of its general usefulness */
6043 /*       Basically a safe strcpy that uses the last character as King */
6044 {
6045     int result = FALSE; int NrPieces;
6046     unsigned char partner[EmptySquare];
6047
6048     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6049                     && NrPieces >= 12 && !(NrPieces&1)) {
6050         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6051
6052         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6053         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6054             char *p, c=0;
6055             if(map[j] == '/') offs = WhitePBishop - i, j++;
6056             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6057             table[i+offs] = map[j++];
6058             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6059             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6060             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6061         }
6062         table[(int) WhiteKing]  = map[j++];
6063         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6064             char *p, c=0;
6065             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6066             i = WHITE_TO_BLACK ii;
6067             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6068             table[i+offs] = map[j++];
6069             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6070             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6071             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6072         }
6073         table[(int) BlackKing]  = map[j++];
6074
6075
6076         if(*escapes) { // set up promotion pairing
6077             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6078             // pieceToChar entirely filled, so we can look up specified partners
6079             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6080                 int c = table[i];
6081                 if(c == '^' || c == '-') { // has specified partner
6082                     int p;
6083                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6084                     if(c == '^') table[i] = '+';
6085                     if(p < EmptySquare) {
6086                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6087                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6088                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6089                     }
6090                 } else if(c == '*') {
6091                     table[i] = partner[i];
6092                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6093                 }
6094             }
6095         }
6096
6097         result = TRUE;
6098     }
6099
6100     return result;
6101 }
6102
6103 int
6104 SetCharTable (unsigned char *table, const char * map)
6105 {
6106     return SetCharTableEsc(table, map, "");
6107 }
6108
6109 void
6110 Prelude (Board board)
6111 {       // [HGM] superchess: random selection of exo-pieces
6112         int i, j, k; ChessSquare p;
6113         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6114
6115         GetPositionNumber(); // use FRC position number
6116
6117         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6118             SetCharTable(pieceToChar, appData.pieceToCharTable);
6119             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6120                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6121         }
6122
6123         j = seed%4;                 seed /= 4;
6124         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6125         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6126         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6127         j = seed%3 + (seed%3 >= j); seed /= 3;
6128         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6129         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6130         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6131         j = seed%3;                 seed /= 3;
6132         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6133         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6134         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6135         j = seed%2 + (seed%2 >= j); seed /= 2;
6136         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6137         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6138         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6139         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6140         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6141         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6142         put(board, exoPieces[0],    0, 0, ANY);
6143         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6144 }
6145
6146 void
6147 InitPosition (int redraw)
6148 {
6149     ChessSquare (* pieces)[BOARD_FILES];
6150     int i, j, pawnRow=1, pieceRows=1, overrule,
6151     oldx = gameInfo.boardWidth,
6152     oldy = gameInfo.boardHeight,
6153     oldh = gameInfo.holdingsWidth;
6154     static int oldv;
6155
6156     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6157
6158     /* [AS] Initialize pv info list [HGM] and game status */
6159     {
6160         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6161             pvInfoList[i].depth = 0;
6162             boards[i][EP_STATUS] = EP_NONE;
6163             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6164         }
6165
6166         initialRulePlies = 0; /* 50-move counter start */
6167
6168         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6169         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6170     }
6171
6172
6173     /* [HGM] logic here is completely changed. In stead of full positions */
6174     /* the initialized data only consist of the two backranks. The switch */
6175     /* selects which one we will use, which is than copied to the Board   */
6176     /* initialPosition, which for the rest is initialized by Pawns and    */
6177     /* empty squares. This initial position is then copied to boards[0],  */
6178     /* possibly after shuffling, so that it remains available.            */
6179
6180     gameInfo.holdingsWidth = 0; /* default board sizes */
6181     gameInfo.boardWidth    = 8;
6182     gameInfo.boardHeight   = 8;
6183     gameInfo.holdingsSize  = 0;
6184     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6185     for(i=0; i<BOARD_FILES-6; i++)
6186       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6187     initialPosition[EP_STATUS] = EP_NONE;
6188     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6189     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6190     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6191          SetCharTable(pieceNickName, appData.pieceNickNames);
6192     else SetCharTable(pieceNickName, "............");
6193     pieces = FIDEArray;
6194
6195     switch (gameInfo.variant) {
6196     case VariantFischeRandom:
6197       shuffleOpenings = TRUE;
6198       appData.fischerCastling = TRUE;
6199     default:
6200       break;
6201     case VariantShatranj:
6202       pieces = ShatranjArray;
6203       nrCastlingRights = 0;
6204       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6205       break;
6206     case VariantMakruk:
6207       pieces = makrukArray;
6208       nrCastlingRights = 0;
6209       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6210       break;
6211     case VariantASEAN:
6212       pieces = aseanArray;
6213       nrCastlingRights = 0;
6214       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6215       break;
6216     case VariantTwoKings:
6217       pieces = twoKingsArray;
6218       break;
6219     case VariantGrand:
6220       pieces = GrandArray;
6221       nrCastlingRights = 0;
6222       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6223       gameInfo.boardWidth = 10;
6224       gameInfo.boardHeight = 10;
6225       gameInfo.holdingsSize = 7;
6226       break;
6227     case VariantCapaRandom:
6228       shuffleOpenings = TRUE;
6229       appData.fischerCastling = TRUE;
6230     case VariantCapablanca:
6231       pieces = CapablancaArray;
6232       gameInfo.boardWidth = 10;
6233       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6234       break;
6235     case VariantGothic:
6236       pieces = GothicArray;
6237       gameInfo.boardWidth = 10;
6238       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6239       break;
6240     case VariantSChess:
6241       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6242       gameInfo.holdingsSize = 7;
6243       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6244       break;
6245     case VariantJanus:
6246       pieces = JanusArray;
6247       gameInfo.boardWidth = 10;
6248       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6249       nrCastlingRights = 6;
6250         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6251         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6252         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6253         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6254         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6255         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6256       break;
6257     case VariantFalcon:
6258       pieces = FalconArray;
6259       gameInfo.boardWidth = 10;
6260       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6261       break;
6262     case VariantXiangqi:
6263       pieces = XiangqiArray;
6264       gameInfo.boardWidth  = 9;
6265       gameInfo.boardHeight = 10;
6266       nrCastlingRights = 0;
6267       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6268       break;
6269     case VariantShogi:
6270       pieces = ShogiArray;
6271       gameInfo.boardWidth  = 9;
6272       gameInfo.boardHeight = 9;
6273       gameInfo.holdingsSize = 7;
6274       nrCastlingRights = 0;
6275       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6276       break;
6277     case VariantChu:
6278       pieces = ChuArray; pieceRows = 3;
6279       gameInfo.boardWidth  = 12;
6280       gameInfo.boardHeight = 12;
6281       nrCastlingRights = 0;
6282       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6283                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6284       break;
6285     case VariantCourier:
6286       pieces = CourierArray;
6287       gameInfo.boardWidth  = 12;
6288       nrCastlingRights = 0;
6289       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6290       break;
6291     case VariantKnightmate:
6292       pieces = KnightmateArray;
6293       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6294       break;
6295     case VariantSpartan:
6296       pieces = SpartanArray;
6297       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6298       break;
6299     case VariantLion:
6300       pieces = lionArray;
6301       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6302       break;
6303     case VariantChuChess:
6304       pieces = ChuChessArray;
6305       gameInfo.boardWidth = 10;
6306       gameInfo.boardHeight = 10;
6307       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6308       break;
6309     case VariantFairy:
6310       pieces = fairyArray;
6311       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6312       break;
6313     case VariantGreat:
6314       pieces = GreatArray;
6315       gameInfo.boardWidth = 10;
6316       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6317       gameInfo.holdingsSize = 8;
6318       break;
6319     case VariantSuper:
6320       pieces = FIDEArray;
6321       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6322       gameInfo.holdingsSize = 8;
6323       startedFromSetupPosition = TRUE;
6324       break;
6325     case VariantCrazyhouse:
6326     case VariantBughouse:
6327       pieces = FIDEArray;
6328       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6329       gameInfo.holdingsSize = 5;
6330       break;
6331     case VariantWildCastle:
6332       pieces = FIDEArray;
6333       /* !!?shuffle with kings guaranteed to be on d or e file */
6334       shuffleOpenings = 1;
6335       break;
6336     case VariantNoCastle:
6337       pieces = FIDEArray;
6338       nrCastlingRights = 0;
6339       /* !!?unconstrained back-rank shuffle */
6340       shuffleOpenings = 1;
6341       break;
6342     }
6343
6344     overrule = 0;
6345     if(appData.NrFiles >= 0) {
6346         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6347         gameInfo.boardWidth = appData.NrFiles;
6348     }
6349     if(appData.NrRanks >= 0) {
6350         gameInfo.boardHeight = appData.NrRanks;
6351     }
6352     if(appData.holdingsSize >= 0) {
6353         i = appData.holdingsSize;
6354         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6355         gameInfo.holdingsSize = i;
6356     }
6357     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6358     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6359         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6360
6361     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6362     if(pawnRow < 1) pawnRow = 1;
6363     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6364        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6365     if(gameInfo.variant == VariantChu) pawnRow = 3;
6366
6367     /* User pieceToChar list overrules defaults */
6368     if(appData.pieceToCharTable != NULL)
6369         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6370
6371     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6372
6373         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6374             s = (ChessSquare) 0; /* account holding counts in guard band */
6375         for( i=0; i<BOARD_HEIGHT; i++ )
6376             initialPosition[i][j] = s;
6377
6378         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6379         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6380         initialPosition[pawnRow][j] = WhitePawn;
6381         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6382         if(gameInfo.variant == VariantXiangqi) {
6383             if(j&1) {
6384                 initialPosition[pawnRow][j] =
6385                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6386                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6387                    initialPosition[2][j] = WhiteCannon;
6388                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6389                 }
6390             }
6391         }
6392         if(gameInfo.variant == VariantChu) {
6393              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6394                initialPosition[pawnRow+1][j] = WhiteCobra,
6395                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6396              for(i=1; i<pieceRows; i++) {
6397                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6398                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6399              }
6400         }
6401         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6402             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6403                initialPosition[0][j] = WhiteRook;
6404                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6405             }
6406         }
6407         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6408     }
6409     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6410     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6411
6412             j=BOARD_LEFT+1;
6413             initialPosition[1][j] = WhiteBishop;
6414             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6415             j=BOARD_RGHT-2;
6416             initialPosition[1][j] = WhiteRook;
6417             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6418     }
6419
6420     if( nrCastlingRights == -1) {
6421         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6422         /*       This sets default castling rights from none to normal corners   */
6423         /* Variants with other castling rights must set them themselves above    */
6424         nrCastlingRights = 6;
6425
6426         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6427         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6428         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6429         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6430         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6431         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6432      }
6433
6434      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6435      if(gameInfo.variant == VariantGreat) { // promotion commoners
6436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6437         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6439         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6440      }
6441      if( gameInfo.variant == VariantSChess ) {
6442       initialPosition[1][0] = BlackMarshall;
6443       initialPosition[2][0] = BlackAngel;
6444       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6445       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6446       initialPosition[1][1] = initialPosition[2][1] =
6447       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6448      }
6449   if (appData.debugMode) {
6450     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6451   }
6452     if(shuffleOpenings) {
6453         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6454         startedFromSetupPosition = TRUE;
6455     }
6456     if(startedFromPositionFile) {
6457       /* [HGM] loadPos: use PositionFile for every new game */
6458       CopyBoard(initialPosition, filePosition);
6459       for(i=0; i<nrCastlingRights; i++)
6460           initialRights[i] = filePosition[CASTLING][i];
6461       startedFromSetupPosition = TRUE;
6462     }
6463     if(*appData.men) LoadPieceDesc(appData.men);
6464
6465     CopyBoard(boards[0], initialPosition);
6466
6467     if(oldx != gameInfo.boardWidth ||
6468        oldy != gameInfo.boardHeight ||
6469        oldv != gameInfo.variant ||
6470        oldh != gameInfo.holdingsWidth
6471                                          )
6472             InitDrawingSizes(-2 ,0);
6473
6474     oldv = gameInfo.variant;
6475     if (redraw)
6476       DrawPosition(TRUE, boards[currentMove]);
6477 }
6478
6479 void
6480 SendBoard (ChessProgramState *cps, int moveNum)
6481 {
6482     char message[MSG_SIZ];
6483
6484     if (cps->useSetboard) {
6485       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6486       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6487       SendToProgram(message, cps);
6488       free(fen);
6489
6490     } else {
6491       ChessSquare *bp;
6492       int i, j, left=0, right=BOARD_WIDTH;
6493       /* Kludge to set black to move, avoiding the troublesome and now
6494        * deprecated "black" command.
6495        */
6496       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6497         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6498
6499       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6500
6501       SendToProgram("edit\n", cps);
6502       SendToProgram("#\n", cps);
6503       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6504         bp = &boards[moveNum][i][left];
6505         for (j = left; j < right; j++, bp++) {
6506           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6507           if ((int) *bp < (int) BlackPawn) {
6508             if(j == BOARD_RGHT+1)
6509                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6510             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6511             if(message[0] == '+' || message[0] == '~') {
6512               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6513                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6514                         AAA + j, ONE + i - '0');
6515             }
6516             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6517                 message[1] = BOARD_RGHT   - 1 - j + '1';
6518                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6519             }
6520             SendToProgram(message, cps);
6521           }
6522         }
6523       }
6524
6525       SendToProgram("c\n", cps);
6526       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6527         bp = &boards[moveNum][i][left];
6528         for (j = left; j < right; j++, bp++) {
6529           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6530           if (((int) *bp != (int) EmptySquare)
6531               && ((int) *bp >= (int) BlackPawn)) {
6532             if(j == BOARD_LEFT-2)
6533                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6534             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6535                     AAA + j, ONE + i - '0');
6536             if(message[0] == '+' || message[0] == '~') {
6537               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6538                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6539                         AAA + j, ONE + i - '0');
6540             }
6541             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6542                 message[1] = BOARD_RGHT   - 1 - j + '1';
6543                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6544             }
6545             SendToProgram(message, cps);
6546           }
6547         }
6548       }
6549
6550       SendToProgram(".\n", cps);
6551     }
6552     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6553 }
6554
6555 char exclusionHeader[MSG_SIZ];
6556 int exCnt, excludePtr;
6557 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6558 static Exclusion excluTab[200];
6559 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6560
6561 static void
6562 WriteMap (int s)
6563 {
6564     int j;
6565     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6566     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6567 }
6568
6569 static void
6570 ClearMap ()
6571 {
6572     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6573     excludePtr = 24; exCnt = 0;
6574     WriteMap(0);
6575 }
6576
6577 static void
6578 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6579 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6580     char buf[2*MOVE_LEN], *p;
6581     Exclusion *e = excluTab;
6582     int i;
6583     for(i=0; i<exCnt; i++)
6584         if(e[i].ff == fromX && e[i].fr == fromY &&
6585            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6586     if(i == exCnt) { // was not in exclude list; add it
6587         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6588         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6589             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6590             return; // abort
6591         }
6592         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6593         excludePtr++; e[i].mark = excludePtr++;
6594         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6595         exCnt++;
6596     }
6597     exclusionHeader[e[i].mark] = state;
6598 }
6599
6600 static int
6601 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6602 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6603     char buf[MSG_SIZ];
6604     int j, k;
6605     ChessMove moveType;
6606     if((signed char)promoChar == -1) { // kludge to indicate best move
6607         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6608             return 1; // if unparsable, abort
6609     }
6610     // update exclusion map (resolving toggle by consulting existing state)
6611     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6612     j = k%8; k >>= 3;
6613     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6614     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6615          excludeMap[k] |=   1<<j;
6616     else excludeMap[k] &= ~(1<<j);
6617     // update header
6618     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6619     // inform engine
6620     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6621     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6622     SendToBoth(buf);
6623     return (state == '+');
6624 }
6625
6626 static void
6627 ExcludeClick (int index)
6628 {
6629     int i, j;
6630     Exclusion *e = excluTab;
6631     if(index < 25) { // none, best or tail clicked
6632         if(index < 13) { // none: include all
6633             WriteMap(0); // clear map
6634             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6635             SendToBoth("include all\n"); // and inform engine
6636         } else if(index > 18) { // tail
6637             if(exclusionHeader[19] == '-') { // tail was excluded
6638                 SendToBoth("include all\n");
6639                 WriteMap(0); // clear map completely
6640                 // now re-exclude selected moves
6641                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6642                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6643             } else { // tail was included or in mixed state
6644                 SendToBoth("exclude all\n");
6645                 WriteMap(0xFF); // fill map completely
6646                 // now re-include selected moves
6647                 j = 0; // count them
6648                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6649                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6650                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6651             }
6652         } else { // best
6653             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6654         }
6655     } else {
6656         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6657             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6658             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6659             break;
6660         }
6661     }
6662 }
6663
6664 ChessSquare
6665 DefaultPromoChoice (int white)
6666 {
6667     ChessSquare result;
6668     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6669        gameInfo.variant == VariantMakruk)
6670         result = WhiteFerz; // no choice
6671     else if(gameInfo.variant == VariantASEAN)
6672         result = WhiteRook; // no choice
6673     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6674         result= WhiteKing; // in Suicide Q is the last thing we want
6675     else if(gameInfo.variant == VariantSpartan)
6676         result = white ? WhiteQueen : WhiteAngel;
6677     else result = WhiteQueen;
6678     if(!white) result = WHITE_TO_BLACK result;
6679     return result;
6680 }
6681
6682 static int autoQueen; // [HGM] oneclick
6683
6684 int
6685 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6686 {
6687     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6688     /* [HGM] add Shogi promotions */
6689     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6690     ChessSquare piece, partner;
6691     ChessMove moveType;
6692     Boolean premove;
6693
6694     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6695     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6696
6697     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6698       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6699         return FALSE;
6700
6701     piece = boards[currentMove][fromY][fromX];
6702     if(gameInfo.variant == VariantChu) {
6703         promotionZoneSize = BOARD_HEIGHT/3;
6704         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6705     } else if(gameInfo.variant == VariantShogi) {
6706         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6707         highestPromotingPiece = (int)WhiteAlfil;
6708     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6709         promotionZoneSize = 3;
6710     }
6711
6712     // Treat Lance as Pawn when it is not representing Amazon or Lance
6713     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6714         if(piece == WhiteLance) piece = WhitePawn; else
6715         if(piece == BlackLance) piece = BlackPawn;
6716     }
6717
6718     // next weed out all moves that do not touch the promotion zone at all
6719     if((int)piece >= BlackPawn) {
6720         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6721              return FALSE;
6722         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6723         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6724     } else {
6725         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6726            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6727         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6728              return FALSE;
6729     }
6730
6731     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6732
6733     // weed out mandatory Shogi promotions
6734     if(gameInfo.variant == VariantShogi) {
6735         if(piece >= BlackPawn) {
6736             if(toY == 0 && piece == BlackPawn ||
6737                toY == 0 && piece == BlackQueen ||
6738                toY <= 1 && piece == BlackKnight) {
6739                 *promoChoice = '+';
6740                 return FALSE;
6741             }
6742         } else {
6743             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6744                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6745                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6746                 *promoChoice = '+';
6747                 return FALSE;
6748             }
6749         }
6750     }
6751
6752     // weed out obviously illegal Pawn moves
6753     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6754         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6755         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6756         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6757         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6758         // note we are not allowed to test for valid (non-)capture, due to premove
6759     }
6760
6761     // we either have a choice what to promote to, or (in Shogi) whether to promote
6762     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6763        gameInfo.variant == VariantMakruk) {
6764         ChessSquare p=BlackFerz;  // no choice
6765         while(p < EmptySquare) {  //but make sure we use piece that exists
6766             *promoChoice = PieceToChar(p++);
6767             if(*promoChoice != '.') break;
6768         }
6769         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6770     }
6771     // no sense asking what we must promote to if it is going to explode...
6772     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6773         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6774         return FALSE;
6775     }
6776     // give caller the default choice even if we will not make it
6777     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6778     partner = piece; // pieces can promote if the pieceToCharTable says so
6779     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6780     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6781     if(        sweepSelect && gameInfo.variant != VariantGreat
6782                            && gameInfo.variant != VariantGrand
6783                            && gameInfo.variant != VariantSuper) return FALSE;
6784     if(autoQueen) return FALSE; // predetermined
6785
6786     // suppress promotion popup on illegal moves that are not premoves
6787     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6788               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6789     if(appData.testLegality && !premove) {
6790         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6791                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6792         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6793         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6794             return FALSE;
6795     }
6796
6797     return TRUE;
6798 }
6799
6800 int
6801 InPalace (int row, int column)
6802 {   /* [HGM] for Xiangqi */
6803     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6804          column < (BOARD_WIDTH + 4)/2 &&
6805          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6806     return FALSE;
6807 }
6808
6809 int
6810 PieceForSquare (int x, int y)
6811 {
6812   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6813      return -1;
6814   else
6815      return boards[currentMove][y][x];
6816 }
6817
6818 int
6819 OKToStartUserMove (int x, int y)
6820 {
6821     ChessSquare from_piece;
6822     int white_piece;
6823
6824     if (matchMode) return FALSE;
6825     if (gameMode == EditPosition) return TRUE;
6826
6827     if (x >= 0 && y >= 0)
6828       from_piece = boards[currentMove][y][x];
6829     else
6830       from_piece = EmptySquare;
6831
6832     if (from_piece == EmptySquare) return FALSE;
6833
6834     white_piece = (int)from_piece >= (int)WhitePawn &&
6835       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6836
6837     switch (gameMode) {
6838       case AnalyzeFile:
6839       case TwoMachinesPlay:
6840       case EndOfGame:
6841         return FALSE;
6842
6843       case IcsObserving:
6844       case IcsIdle:
6845         return FALSE;
6846
6847       case MachinePlaysWhite:
6848       case IcsPlayingBlack:
6849         if (appData.zippyPlay) return FALSE;
6850         if (white_piece) {
6851             DisplayMoveError(_("You are playing Black"));
6852             return FALSE;
6853         }
6854         break;
6855
6856       case MachinePlaysBlack:
6857       case IcsPlayingWhite:
6858         if (appData.zippyPlay) return FALSE;
6859         if (!white_piece) {
6860             DisplayMoveError(_("You are playing White"));
6861             return FALSE;
6862         }
6863         break;
6864
6865       case PlayFromGameFile:
6866             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6867       case EditGame:
6868       case AnalyzeMode:
6869         if (!white_piece && WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is White's turn"));
6871             return FALSE;
6872         }
6873         if (white_piece && !WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is Black's turn"));
6875             return FALSE;
6876         }
6877         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6878             /* Editing correspondence game history */
6879             /* Could disallow this or prompt for confirmation */
6880             cmailOldMove = -1;
6881         }
6882         break;
6883
6884       case BeginningOfGame:
6885         if (appData.icsActive) return FALSE;
6886         if (!appData.noChessProgram) {
6887             if (!white_piece) {
6888                 DisplayMoveError(_("You are playing White"));
6889                 return FALSE;
6890             }
6891         }
6892         break;
6893
6894       case Training:
6895         if (!white_piece && WhiteOnMove(currentMove)) {
6896             DisplayMoveError(_("It is White's turn"));
6897             return FALSE;
6898         }
6899         if (white_piece && !WhiteOnMove(currentMove)) {
6900             DisplayMoveError(_("It is Black's turn"));
6901             return FALSE;
6902         }
6903         break;
6904
6905       default:
6906       case IcsExamining:
6907         break;
6908     }
6909     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6910         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6911         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6912         && gameMode != AnalyzeFile && gameMode != Training) {
6913         DisplayMoveError(_("Displayed position is not current"));
6914         return FALSE;
6915     }
6916     return TRUE;
6917 }
6918
6919 Boolean
6920 OnlyMove (int *x, int *y, Boolean captures)
6921 {
6922     DisambiguateClosure cl;
6923     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6924     switch(gameMode) {
6925       case MachinePlaysBlack:
6926       case IcsPlayingWhite:
6927       case BeginningOfGame:
6928         if(!WhiteOnMove(currentMove)) return FALSE;
6929         break;
6930       case MachinePlaysWhite:
6931       case IcsPlayingBlack:
6932         if(WhiteOnMove(currentMove)) return FALSE;
6933         break;
6934       case EditGame:
6935         break;
6936       default:
6937         return FALSE;
6938     }
6939     cl.pieceIn = EmptySquare;
6940     cl.rfIn = *y;
6941     cl.ffIn = *x;
6942     cl.rtIn = -1;
6943     cl.ftIn = -1;
6944     cl.promoCharIn = NULLCHAR;
6945     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6946     if( cl.kind == NormalMove ||
6947         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6948         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6949         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6950       fromX = cl.ff;
6951       fromY = cl.rf;
6952       *x = cl.ft;
6953       *y = cl.rt;
6954       return TRUE;
6955     }
6956     if(cl.kind != ImpossibleMove) return FALSE;
6957     cl.pieceIn = EmptySquare;
6958     cl.rfIn = -1;
6959     cl.ffIn = -1;
6960     cl.rtIn = *y;
6961     cl.ftIn = *x;
6962     cl.promoCharIn = NULLCHAR;
6963     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6964     if( cl.kind == NormalMove ||
6965         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6966         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6967         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6968       fromX = cl.ff;
6969       fromY = cl.rf;
6970       *x = cl.ft;
6971       *y = cl.rt;
6972       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6973       return TRUE;
6974     }
6975     return FALSE;
6976 }
6977
6978 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6979 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6980 int lastLoadGameUseList = FALSE;
6981 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6982 ChessMove lastLoadGameStart = EndOfFile;
6983 int doubleClick;
6984 Boolean addToBookFlag;
6985
6986 void
6987 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6988 {
6989     ChessMove moveType;
6990     ChessSquare pup;
6991     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6992
6993     /* Check if the user is playing in turn.  This is complicated because we
6994        let the user "pick up" a piece before it is his turn.  So the piece he
6995        tried to pick up may have been captured by the time he puts it down!
6996        Therefore we use the color the user is supposed to be playing in this
6997        test, not the color of the piece that is currently on the starting
6998        square---except in EditGame mode, where the user is playing both
6999        sides; fortunately there the capture race can't happen.  (It can
7000        now happen in IcsExamining mode, but that's just too bad.  The user
7001        will get a somewhat confusing message in that case.)
7002        */
7003
7004     switch (gameMode) {
7005       case AnalyzeFile:
7006       case TwoMachinesPlay:
7007       case EndOfGame:
7008       case IcsObserving:
7009       case IcsIdle:
7010         /* We switched into a game mode where moves are not accepted,
7011            perhaps while the mouse button was down. */
7012         return;
7013
7014       case MachinePlaysWhite:
7015         /* User is moving for Black */
7016         if (WhiteOnMove(currentMove)) {
7017             DisplayMoveError(_("It is White's turn"));
7018             return;
7019         }
7020         break;
7021
7022       case MachinePlaysBlack:
7023         /* User is moving for White */
7024         if (!WhiteOnMove(currentMove)) {
7025             DisplayMoveError(_("It is Black's turn"));
7026             return;
7027         }
7028         break;
7029
7030       case PlayFromGameFile:
7031             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7032       case EditGame:
7033       case IcsExamining:
7034       case BeginningOfGame:
7035       case AnalyzeMode:
7036       case Training:
7037         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7038         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7039             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7040             /* User is moving for Black */
7041             if (WhiteOnMove(currentMove)) {
7042                 DisplayMoveError(_("It is White's turn"));
7043                 return;
7044             }
7045         } else {
7046             /* User is moving for White */
7047             if (!WhiteOnMove(currentMove)) {
7048                 DisplayMoveError(_("It is Black's turn"));
7049                 return;
7050             }
7051         }
7052         break;
7053
7054       case IcsPlayingBlack:
7055         /* User is moving for Black */
7056         if (WhiteOnMove(currentMove)) {
7057             if (!appData.premove) {
7058                 DisplayMoveError(_("It is White's turn"));
7059             } else if (toX >= 0 && toY >= 0) {
7060                 premoveToX = toX;
7061                 premoveToY = toY;
7062                 premoveFromX = fromX;
7063                 premoveFromY = fromY;
7064                 premovePromoChar = promoChar;
7065                 gotPremove = 1;
7066                 if (appData.debugMode)
7067                     fprintf(debugFP, "Got premove: fromX %d,"
7068                             "fromY %d, toX %d, toY %d\n",
7069                             fromX, fromY, toX, toY);
7070             }
7071             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7072             return;
7073         }
7074         break;
7075
7076       case IcsPlayingWhite:
7077         /* User is moving for White */
7078         if (!WhiteOnMove(currentMove)) {
7079             if (!appData.premove) {
7080                 DisplayMoveError(_("It is Black's turn"));
7081             } else if (toX >= 0 && toY >= 0) {
7082                 premoveToX = toX;
7083                 premoveToY = toY;
7084                 premoveFromX = fromX;
7085                 premoveFromY = fromY;
7086                 premovePromoChar = promoChar;
7087                 gotPremove = 1;
7088                 if (appData.debugMode)
7089                     fprintf(debugFP, "Got premove: fromX %d,"
7090                             "fromY %d, toX %d, toY %d\n",
7091                             fromX, fromY, toX, toY);
7092             }
7093             DrawPosition(TRUE, boards[currentMove]);
7094             return;
7095         }
7096         break;
7097
7098       default:
7099         break;
7100
7101       case EditPosition:
7102         /* EditPosition, empty square, or different color piece;
7103            click-click move is possible */
7104         if (toX == -2 || toY == -2) {
7105             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7106             DrawPosition(FALSE, boards[currentMove]);
7107             return;
7108         } else if (toX >= 0 && toY >= 0) {
7109             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7110                 ChessSquare p = boards[0][rf][ff];
7111                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7112                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7113             }
7114             boards[0][toY][toX] = boards[0][fromY][fromX];
7115             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7116                 if(boards[0][fromY][0] != EmptySquare) {
7117                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7118                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7119                 }
7120             } else
7121             if(fromX == BOARD_RGHT+1) {
7122                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7123                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7124                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7125                 }
7126             } else
7127             boards[0][fromY][fromX] = gatingPiece;
7128             ClearHighlights();
7129             DrawPosition(FALSE, boards[currentMove]);
7130             return;
7131         }
7132         return;
7133     }
7134
7135     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7136     pup = boards[currentMove][toY][toX];
7137
7138     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7139     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7140          if( pup != EmptySquare ) return;
7141          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7142            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7143                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7144            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7145            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7146            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7147            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7148          fromY = DROP_RANK;
7149     }
7150
7151     /* [HGM] always test for legality, to get promotion info */
7152     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7153                                          fromY, fromX, toY, toX, promoChar);
7154
7155     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7156
7157     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7158
7159     /* [HGM] but possibly ignore an IllegalMove result */
7160     if (appData.testLegality) {
7161         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7162             DisplayMoveError(_("Illegal move"));
7163             return;
7164         }
7165     }
7166
7167     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7168         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7169              ClearPremoveHighlights(); // was included
7170         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7171         DrawPosition(FALSE, NULL);
7172         return;
7173     }
7174
7175     if(addToBookFlag) { // adding moves to book
7176         char buf[MSG_SIZ], move[MSG_SIZ];
7177         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7178         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7179                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7180         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7181         AddBookMove(buf);
7182         addToBookFlag = FALSE;
7183         ClearHighlights();
7184         return;
7185     }
7186
7187     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7188 }
7189
7190 /* Common tail of UserMoveEvent and DropMenuEvent */
7191 int
7192 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7193 {
7194     char *bookHit = 0;
7195
7196     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7197         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7198         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7199         if(WhiteOnMove(currentMove)) {
7200             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7201         } else {
7202             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7203         }
7204     }
7205
7206     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7207        move type in caller when we know the move is a legal promotion */
7208     if(moveType == NormalMove && promoChar)
7209         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7210
7211     /* [HGM] <popupFix> The following if has been moved here from
7212        UserMoveEvent(). Because it seemed to belong here (why not allow
7213        piece drops in training games?), and because it can only be
7214        performed after it is known to what we promote. */
7215     if (gameMode == Training) {
7216       /* compare the move played on the board to the next move in the
7217        * game. If they match, display the move and the opponent's response.
7218        * If they don't match, display an error message.
7219        */
7220       int saveAnimate;
7221       Board testBoard;
7222       CopyBoard(testBoard, boards[currentMove]);
7223       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7224
7225       if (CompareBoards(testBoard, boards[currentMove+1])) {
7226         ForwardInner(currentMove+1);
7227
7228         /* Autoplay the opponent's response.
7229          * if appData.animate was TRUE when Training mode was entered,
7230          * the response will be animated.
7231          */
7232         saveAnimate = appData.animate;
7233         appData.animate = animateTraining;
7234         ForwardInner(currentMove+1);
7235         appData.animate = saveAnimate;
7236
7237         /* check for the end of the game */
7238         if (currentMove >= forwardMostMove) {
7239           gameMode = PlayFromGameFile;
7240           ModeHighlight();
7241           SetTrainingModeOff();
7242           DisplayInformation(_("End of game"));
7243         }
7244       } else {
7245         DisplayError(_("Incorrect move"), 0);
7246       }
7247       return 1;
7248     }
7249
7250   /* Ok, now we know that the move is good, so we can kill
7251      the previous line in Analysis Mode */
7252   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7253                                 && currentMove < forwardMostMove) {
7254     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7255     else forwardMostMove = currentMove;
7256   }
7257
7258   ClearMap();
7259
7260   /* If we need the chess program but it's dead, restart it */
7261   ResurrectChessProgram();
7262
7263   /* A user move restarts a paused game*/
7264   if (pausing)
7265     PauseEvent();
7266
7267   thinkOutput[0] = NULLCHAR;
7268
7269   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7270
7271   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7272     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7273     return 1;
7274   }
7275
7276   if (gameMode == BeginningOfGame) {
7277     if (appData.noChessProgram) {
7278       gameMode = EditGame;
7279       SetGameInfo();
7280     } else {
7281       char buf[MSG_SIZ];
7282       gameMode = MachinePlaysBlack;
7283       StartClocks();
7284       SetGameInfo();
7285       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7286       DisplayTitle(buf);
7287       if (first.sendName) {
7288         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7289         SendToProgram(buf, &first);
7290       }
7291       StartClocks();
7292     }
7293     ModeHighlight();
7294   }
7295
7296   /* Relay move to ICS or chess engine */
7297   if (appData.icsActive) {
7298     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7299         gameMode == IcsExamining) {
7300       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7301         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7302         SendToICS("draw ");
7303         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7304       }
7305       // also send plain move, in case ICS does not understand atomic claims
7306       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7307       ics_user_moved = 1;
7308     }
7309   } else {
7310     if (first.sendTime && (gameMode == BeginningOfGame ||
7311                            gameMode == MachinePlaysWhite ||
7312                            gameMode == MachinePlaysBlack)) {
7313       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7314     }
7315     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7316          // [HGM] book: if program might be playing, let it use book
7317         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7318         first.maybeThinking = TRUE;
7319     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7320         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7321         SendBoard(&first, currentMove+1);
7322         if(second.analyzing) {
7323             if(!second.useSetboard) SendToProgram("undo\n", &second);
7324             SendBoard(&second, currentMove+1);
7325         }
7326     } else {
7327         SendMoveToProgram(forwardMostMove-1, &first);
7328         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7329     }
7330     if (currentMove == cmailOldMove + 1) {
7331       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7332     }
7333   }
7334
7335   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7336
7337   switch (gameMode) {
7338   case EditGame:
7339     if(appData.testLegality)
7340     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7341     case MT_NONE:
7342     case MT_CHECK:
7343       break;
7344     case MT_CHECKMATE:
7345     case MT_STAINMATE:
7346       if (WhiteOnMove(currentMove)) {
7347         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7348       } else {
7349         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7350       }
7351       break;
7352     case MT_STALEMATE:
7353       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7354       break;
7355     }
7356     break;
7357
7358   case MachinePlaysBlack:
7359   case MachinePlaysWhite:
7360     /* disable certain menu options while machine is thinking */
7361     SetMachineThinkingEnables();
7362     break;
7363
7364   default:
7365     break;
7366   }
7367
7368   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7369   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7370
7371   if(bookHit) { // [HGM] book: simulate book reply
7372         static char bookMove[MSG_SIZ]; // a bit generous?
7373
7374         programStats.nodes = programStats.depth = programStats.time =
7375         programStats.score = programStats.got_only_move = 0;
7376         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7377
7378         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7379         strcat(bookMove, bookHit);
7380         HandleMachineMove(bookMove, &first);
7381   }
7382   return 1;
7383 }
7384
7385 void
7386 MarkByFEN(char *fen)
7387 {
7388         int r, f;
7389         if(!appData.markers || !appData.highlightDragging) return;
7390         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7391         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7392         while(*fen) {
7393             int s = 0;
7394             marker[r][f] = 0;
7395             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7396             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7397             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7398             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7399             if(*fen == 'T') marker[r][f++] = 0; else
7400             if(*fen == 'Y') marker[r][f++] = 1; else
7401             if(*fen == 'G') marker[r][f++] = 3; else
7402             if(*fen == 'B') marker[r][f++] = 4; else
7403             if(*fen == 'C') marker[r][f++] = 5; else
7404             if(*fen == 'M') marker[r][f++] = 6; else
7405             if(*fen == 'W') marker[r][f++] = 7; else
7406             if(*fen == 'D') marker[r][f++] = 8; else
7407             if(*fen == 'R') marker[r][f++] = 2; else {
7408                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7409               f += s; fen -= s>0;
7410             }
7411             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7412             if(r < 0) break;
7413             fen++;
7414         }
7415         DrawPosition(TRUE, NULL);
7416 }
7417
7418 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7419
7420 void
7421 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7422 {
7423     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7424     Markers *m = (Markers *) closure;
7425     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7426                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7427         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7428                          || kind == WhiteCapturesEnPassant
7429                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7430     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7431 }
7432
7433 static int hoverSavedValid;
7434
7435 void
7436 MarkTargetSquares (int clear)
7437 {
7438   int x, y, sum=0;
7439   if(clear) { // no reason to ever suppress clearing
7440     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7441     hoverSavedValid = 0;
7442     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7443   } else {
7444     int capt = 0;
7445     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7446        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7447     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7448     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7449       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7450       if(capt)
7451       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7452     }
7453   }
7454   DrawPosition(FALSE, NULL);
7455 }
7456
7457 int
7458 Explode (Board board, int fromX, int fromY, int toX, int toY)
7459 {
7460     if(gameInfo.variant == VariantAtomic &&
7461        (board[toY][toX] != EmptySquare ||                     // capture?
7462         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7463                          board[fromY][fromX] == BlackPawn   )
7464       )) {
7465         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7466         return TRUE;
7467     }
7468     return FALSE;
7469 }
7470
7471 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7472
7473 int
7474 CanPromote (ChessSquare piece, int y)
7475 {
7476         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7477         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7478         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7479         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7480            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7481           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7482            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7483         return (piece == BlackPawn && y <= zone ||
7484                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7485                 piece == BlackLance && y <= zone ||
7486                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7487 }
7488
7489 void
7490 HoverEvent (int xPix, int yPix, int x, int y)
7491 {
7492         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7493         int r, f;
7494         if(!first.highlight) return;
7495         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7496         if(x == oldX && y == oldY) return; // only do something if we enter new square
7497         oldFromX = fromX; oldFromY = fromY;
7498         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7499           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7500             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7501           hoverSavedValid = 1;
7502         } else if(oldX != x || oldY != y) {
7503           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7504           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7505           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7506             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7507           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7508             char buf[MSG_SIZ];
7509             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7510             SendToProgram(buf, &first);
7511           }
7512           oldX = x; oldY = y;
7513 //        SetHighlights(fromX, fromY, x, y);
7514         }
7515 }
7516
7517 void ReportClick(char *action, int x, int y)
7518 {
7519         char buf[MSG_SIZ]; // Inform engine of what user does
7520         int r, f;
7521         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7522           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7523             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7524         if(!first.highlight || gameMode == EditPosition) return;
7525         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7526         SendToProgram(buf, &first);
7527 }
7528
7529 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7530
7531 void
7532 LeftClick (ClickType clickType, int xPix, int yPix)
7533 {
7534     int x, y;
7535     Boolean saveAnimate;
7536     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7537     char promoChoice = NULLCHAR;
7538     ChessSquare piece;
7539     static TimeMark lastClickTime, prevClickTime;
7540
7541     if(flashing) return;
7542
7543     x = EventToSquare(xPix, BOARD_WIDTH);
7544     y = EventToSquare(yPix, BOARD_HEIGHT);
7545     if (!flipView && y >= 0) {
7546         y = BOARD_HEIGHT - 1 - y;
7547     }
7548     if (flipView && x >= 0) {
7549         x = BOARD_WIDTH - 1 - x;
7550     }
7551
7552     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7553         static int dummy;
7554         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7555         right = TRUE;
7556         return;
7557     }
7558
7559     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7560
7561     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7562
7563     if (clickType == Press) ErrorPopDown();
7564     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7565
7566     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7567         defaultPromoChoice = promoSweep;
7568         promoSweep = EmptySquare;   // terminate sweep
7569         promoDefaultAltered = TRUE;
7570         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7571     }
7572
7573     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7574         if(clickType == Release) return; // ignore upclick of click-click destination
7575         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7576         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7577         if(gameInfo.holdingsWidth &&
7578                 (WhiteOnMove(currentMove)
7579                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7580                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7581             // click in right holdings, for determining promotion piece
7582             ChessSquare p = boards[currentMove][y][x];
7583             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7584             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7585             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7586                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7587                 fromX = fromY = -1;
7588                 return;
7589             }
7590         }
7591         DrawPosition(FALSE, boards[currentMove]);
7592         return;
7593     }
7594
7595     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7596     if(clickType == Press
7597             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7598               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7599               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7600         return;
7601
7602     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7603         // could be static click on premove from-square: abort premove
7604         gotPremove = 0;
7605         ClearPremoveHighlights();
7606     }
7607
7608     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7609         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7610
7611     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7612         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7613                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7614         defaultPromoChoice = DefaultPromoChoice(side);
7615     }
7616
7617     autoQueen = appData.alwaysPromoteToQueen;
7618
7619     if (fromX == -1) {
7620       int originalY = y;
7621       gatingPiece = EmptySquare;
7622       if (clickType != Press) {
7623         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7624             DragPieceEnd(xPix, yPix); dragging = 0;
7625             DrawPosition(FALSE, NULL);
7626         }
7627         return;
7628       }
7629       doubleClick = FALSE;
7630       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7631         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7632       }
7633       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7634       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7635          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7636          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7637             /* First square */
7638             if (OKToStartUserMove(fromX, fromY)) {
7639                 second = 0;
7640                 ReportClick("lift", x, y);
7641                 MarkTargetSquares(0);
7642                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7643                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7644                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7645                     promoSweep = defaultPromoChoice;
7646                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7647                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7648                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7649                 }
7650                 if (appData.highlightDragging) {
7651                     SetHighlights(fromX, fromY, -1, -1);
7652                 } else {
7653                     ClearHighlights();
7654                 }
7655             } else fromX = fromY = -1;
7656             return;
7657         }
7658     }
7659
7660     /* fromX != -1 */
7661     if (clickType == Press && gameMode != EditPosition) {
7662         ChessSquare fromP;
7663         ChessSquare toP;
7664         int frc;
7665
7666         // ignore off-board to clicks
7667         if(y < 0 || x < 0) return;
7668
7669         /* Check if clicking again on the same color piece */
7670         fromP = boards[currentMove][fromY][fromX];
7671         toP = boards[currentMove][y][x];
7672         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7673         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7674             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7675            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7676              WhitePawn <= toP && toP <= WhiteKing &&
7677              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7678              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7679             (BlackPawn <= fromP && fromP <= BlackKing &&
7680              BlackPawn <= toP && toP <= BlackKing &&
7681              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7682              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7683             /* Clicked again on same color piece -- changed his mind */
7684             second = (x == fromX && y == fromY);
7685             killX = killY = kill2X = kill2Y = -1;
7686             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7687                 second = FALSE; // first double-click rather than scond click
7688                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7689             }
7690             promoDefaultAltered = FALSE;
7691            if(!second) MarkTargetSquares(1);
7692            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7693             if (appData.highlightDragging) {
7694                 SetHighlights(x, y, -1, -1);
7695             } else {
7696                 ClearHighlights();
7697             }
7698             if (OKToStartUserMove(x, y)) {
7699                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7700                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7701                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7702                  gatingPiece = boards[currentMove][fromY][fromX];
7703                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7704                 fromX = x;
7705                 fromY = y; dragging = 1;
7706                 if(!second) ReportClick("lift", x, y);
7707                 MarkTargetSquares(0);
7708                 DragPieceBegin(xPix, yPix, FALSE);
7709                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7710                     promoSweep = defaultPromoChoice;
7711                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7712                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7713                 }
7714             }
7715            }
7716            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7717            second = FALSE;
7718         }
7719         // ignore clicks on holdings
7720         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7721     }
7722
7723     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7724         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7725         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7726         return;
7727     }
7728
7729     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7730         DragPieceEnd(xPix, yPix); dragging = 0;
7731         if(clearFlag) {
7732             // a deferred attempt to click-click move an empty square on top of a piece
7733             boards[currentMove][y][x] = EmptySquare;
7734             ClearHighlights();
7735             DrawPosition(FALSE, boards[currentMove]);
7736             fromX = fromY = -1; clearFlag = 0;
7737             return;
7738         }
7739         if (appData.animateDragging) {
7740             /* Undo animation damage if any */
7741             DrawPosition(FALSE, NULL);
7742         }
7743         if (second) {
7744             /* Second up/down in same square; just abort move */
7745             second = 0;
7746             fromX = fromY = -1;
7747             gatingPiece = EmptySquare;
7748             ClearHighlights();
7749             gotPremove = 0;
7750             ClearPremoveHighlights();
7751             MarkTargetSquares(-1);
7752             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7753         } else {
7754             /* First upclick in same square; start click-click mode */
7755             SetHighlights(x, y, -1, -1);
7756         }
7757         return;
7758     }
7759
7760     clearFlag = 0;
7761
7762     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7763        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7764         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7765         DisplayMessage(_("only marked squares are legal"),"");
7766         DrawPosition(TRUE, NULL);
7767         return; // ignore to-click
7768     }
7769
7770     /* we now have a different from- and (possibly off-board) to-square */
7771     /* Completed move */
7772     if(!sweepSelecting) {
7773         toX = x;
7774         toY = y;
7775     }
7776
7777     piece = boards[currentMove][fromY][fromX];
7778
7779     saveAnimate = appData.animate;
7780     if (clickType == Press) {
7781         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7782         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7783             // must be Edit Position mode with empty-square selected
7784             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7785             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7786             return;
7787         }
7788         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7789             return;
7790         }
7791         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7792             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7793         } else
7794         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7795         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7796           if(appData.sweepSelect) {
7797             promoSweep = defaultPromoChoice;
7798             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7799             selectFlag = 0; lastX = xPix; lastY = yPix;
7800             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7801             saveFlash = appData.flashCount; appData.flashCount = 0;
7802             Sweep(0); // Pawn that is going to promote: preview promotion piece
7803             sweepSelecting = 1;
7804             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7805             MarkTargetSquares(1);
7806           }
7807           return; // promo popup appears on up-click
7808         }
7809         /* Finish clickclick move */
7810         if (appData.animate || appData.highlightLastMove) {
7811             SetHighlights(fromX, fromY, toX, toY);
7812         } else {
7813             ClearHighlights();
7814         }
7815         MarkTargetSquares(1);
7816     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7817         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7818         *promoRestrict = 0; appData.flashCount = saveFlash;
7819         if (appData.animate || appData.highlightLastMove) {
7820             SetHighlights(fromX, fromY, toX, toY);
7821         } else {
7822             ClearHighlights();
7823         }
7824         MarkTargetSquares(1);
7825     } else {
7826 #if 0
7827 // [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
7828         /* Finish drag move */
7829         if (appData.highlightLastMove) {
7830             SetHighlights(fromX, fromY, toX, toY);
7831         } else {
7832             ClearHighlights();
7833         }
7834 #endif
7835         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7836           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7837         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7838         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7839           dragging *= 2;            // flag button-less dragging if we are dragging
7840           MarkTargetSquares(1);
7841           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7842           else {
7843             kill2X = killX; kill2Y = killY;
7844             killX = x; killY = y;     // remember this square as intermediate
7845             ReportClick("put", x, y); // and inform engine
7846             ReportClick("lift", x, y);
7847             MarkTargetSquares(0);
7848             return;
7849           }
7850         }
7851         DragPieceEnd(xPix, yPix); dragging = 0;
7852         /* Don't animate move and drag both */
7853         appData.animate = FALSE;
7854         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7855     }
7856
7857     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7858     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7859         ChessSquare piece = boards[currentMove][fromY][fromX];
7860         if(gameMode == EditPosition && piece != EmptySquare &&
7861            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7862             int n;
7863
7864             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7865                 n = PieceToNumber(piece - (int)BlackPawn);
7866                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7867                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7868                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7869             } else
7870             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7871                 n = PieceToNumber(piece);
7872                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7873                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7874                 boards[currentMove][n][BOARD_WIDTH-2]++;
7875             }
7876             boards[currentMove][fromY][fromX] = EmptySquare;
7877         }
7878         ClearHighlights();
7879         fromX = fromY = -1;
7880         MarkTargetSquares(1);
7881         DrawPosition(TRUE, boards[currentMove]);
7882         return;
7883     }
7884
7885     // off-board moves should not be highlighted
7886     if(x < 0 || y < 0) ClearHighlights();
7887     else ReportClick("put", x, y);
7888
7889     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7890
7891     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7892
7893     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7894         SetHighlights(fromX, fromY, toX, toY);
7895         MarkTargetSquares(1);
7896         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7897             // [HGM] super: promotion to captured piece selected from holdings
7898             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7899             promotionChoice = TRUE;
7900             // kludge follows to temporarily execute move on display, without promoting yet
7901             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7902             boards[currentMove][toY][toX] = p;
7903             DrawPosition(FALSE, boards[currentMove]);
7904             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7905             boards[currentMove][toY][toX] = q;
7906             DisplayMessage("Click in holdings to choose piece", "");
7907             return;
7908         }
7909         PromotionPopUp(promoChoice);
7910     } else {
7911         int oldMove = currentMove;
7912         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7913         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7914         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7915         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7916         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7917            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7918             DrawPosition(TRUE, boards[currentMove]);
7919         fromX = fromY = -1;
7920         flashing = 0;
7921     }
7922     appData.animate = saveAnimate;
7923     if (appData.animate || appData.animateDragging) {
7924         /* Undo animation damage if needed */
7925 //      DrawPosition(FALSE, NULL);
7926     }
7927 }
7928
7929 int
7930 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7931 {   // front-end-free part taken out of PieceMenuPopup
7932     int whichMenu; int xSqr, ySqr;
7933
7934     if(seekGraphUp) { // [HGM] seekgraph
7935         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7936         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7937         return -2;
7938     }
7939
7940     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7941          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7942         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7943         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7944         if(action == Press)   {
7945             originalFlip = flipView;
7946             flipView = !flipView; // temporarily flip board to see game from partners perspective
7947             DrawPosition(TRUE, partnerBoard);
7948             DisplayMessage(partnerStatus, "");
7949             partnerUp = TRUE;
7950         } else if(action == Release) {
7951             flipView = originalFlip;
7952             DrawPosition(TRUE, boards[currentMove]);
7953             partnerUp = FALSE;
7954         }
7955         return -2;
7956     }
7957
7958     xSqr = EventToSquare(x, BOARD_WIDTH);
7959     ySqr = EventToSquare(y, BOARD_HEIGHT);
7960     if (action == Release) {
7961         if(pieceSweep != EmptySquare) {
7962             EditPositionMenuEvent(pieceSweep, toX, toY);
7963             pieceSweep = EmptySquare;
7964         } else UnLoadPV(); // [HGM] pv
7965     }
7966     if (action != Press) return -2; // return code to be ignored
7967     switch (gameMode) {
7968       case IcsExamining:
7969         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7970       case EditPosition:
7971         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7972         if (xSqr < 0 || ySqr < 0) return -1;
7973         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7974         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7975         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7976         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7977         NextPiece(0);
7978         return 2; // grab
7979       case IcsObserving:
7980         if(!appData.icsEngineAnalyze) return -1;
7981       case IcsPlayingWhite:
7982       case IcsPlayingBlack:
7983         if(!appData.zippyPlay) goto noZip;
7984       case AnalyzeMode:
7985       case AnalyzeFile:
7986       case MachinePlaysWhite:
7987       case MachinePlaysBlack:
7988       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7989         if (!appData.dropMenu) {
7990           LoadPV(x, y);
7991           return 2; // flag front-end to grab mouse events
7992         }
7993         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7994            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7995       case EditGame:
7996       noZip:
7997         if (xSqr < 0 || ySqr < 0) return -1;
7998         if (!appData.dropMenu || appData.testLegality &&
7999             gameInfo.variant != VariantBughouse &&
8000             gameInfo.variant != VariantCrazyhouse) return -1;
8001         whichMenu = 1; // drop menu
8002         break;
8003       default:
8004         return -1;
8005     }
8006
8007     if (((*fromX = xSqr) < 0) ||
8008         ((*fromY = ySqr) < 0)) {
8009         *fromX = *fromY = -1;
8010         return -1;
8011     }
8012     if (flipView)
8013       *fromX = BOARD_WIDTH - 1 - *fromX;
8014     else
8015       *fromY = BOARD_HEIGHT - 1 - *fromY;
8016
8017     return whichMenu;
8018 }
8019
8020 void
8021 Wheel (int dir, int x, int y)
8022 {
8023     if(gameMode == EditPosition) {
8024         int xSqr = EventToSquare(x, BOARD_WIDTH);
8025         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8026         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8027         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8028         do {
8029             boards[currentMove][ySqr][xSqr] += dir;
8030             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8031             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8032         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8033         DrawPosition(FALSE, boards[currentMove]);
8034     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8035 }
8036
8037 void
8038 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8039 {
8040 //    char * hint = lastHint;
8041     FrontEndProgramStats stats;
8042
8043     stats.which = cps == &first ? 0 : 1;
8044     stats.depth = cpstats->depth;
8045     stats.nodes = cpstats->nodes;
8046     stats.score = cpstats->score;
8047     stats.time = cpstats->time;
8048     stats.pv = cpstats->movelist;
8049     stats.hint = lastHint;
8050     stats.an_move_index = 0;
8051     stats.an_move_count = 0;
8052
8053     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8054         stats.hint = cpstats->move_name;
8055         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8056         stats.an_move_count = cpstats->nr_moves;
8057     }
8058
8059     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
8060
8061     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8062         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8063
8064     SetProgramStats( &stats );
8065 }
8066
8067 void
8068 ClearEngineOutputPane (int which)
8069 {
8070     static FrontEndProgramStats dummyStats;
8071     dummyStats.which = which;
8072     dummyStats.pv = "#";
8073     SetProgramStats( &dummyStats );
8074 }
8075
8076 #define MAXPLAYERS 500
8077
8078 char *
8079 TourneyStandings (int display)
8080 {
8081     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8082     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8083     char result, *p, *names[MAXPLAYERS];
8084
8085     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8086         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8087     names[0] = p = strdup(appData.participants);
8088     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8089
8090     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8091
8092     while(result = appData.results[nr]) {
8093         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8094         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8095         wScore = bScore = 0;
8096         switch(result) {
8097           case '+': wScore = 2; break;
8098           case '-': bScore = 2; break;
8099           case '=': wScore = bScore = 1; break;
8100           case ' ':
8101           case '*': return strdup("busy"); // tourney not finished
8102         }
8103         score[w] += wScore;
8104         score[b] += bScore;
8105         games[w]++;
8106         games[b]++;
8107         nr++;
8108     }
8109     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8110     for(w=0; w<nPlayers; w++) {
8111         bScore = -1;
8112         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8113         ranking[w] = b; points[w] = bScore; score[b] = -2;
8114     }
8115     p = malloc(nPlayers*34+1);
8116     for(w=0; w<nPlayers && w<display; w++)
8117         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8118     free(names[0]);
8119     return p;
8120 }
8121
8122 void
8123 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8124 {       // count all piece types
8125         int p, f, r;
8126         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8127         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8128         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8129                 p = board[r][f];
8130                 pCnt[p]++;
8131                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8132                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8133                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8134                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8135                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8136                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8137         }
8138 }
8139
8140 int
8141 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8142 {
8143         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8144         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8145
8146         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8147         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8148         if(myPawns == 2 && nMine == 3) // KPP
8149             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8150         if(myPawns == 1 && nMine == 2) // KP
8151             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8152         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8153             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8154         if(myPawns) return FALSE;
8155         if(pCnt[WhiteRook+side])
8156             return pCnt[BlackRook-side] ||
8157                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8158                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8159                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8160         if(pCnt[WhiteCannon+side]) {
8161             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8162             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8163         }
8164         if(pCnt[WhiteKnight+side])
8165             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8166         return FALSE;
8167 }
8168
8169 int
8170 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8171 {
8172         VariantClass v = gameInfo.variant;
8173
8174         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8175         if(v == VariantShatranj) return TRUE; // always winnable through baring
8176         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8177         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8178
8179         if(v == VariantXiangqi) {
8180                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8181
8182                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8183                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8184                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8185                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8186                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8187                 if(stale) // we have at least one last-rank P plus perhaps C
8188                     return majors // KPKX
8189                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8190                 else // KCA*E*
8191                     return pCnt[WhiteFerz+side] // KCAK
8192                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8193                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8194                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8195
8196         } else if(v == VariantKnightmate) {
8197                 if(nMine == 1) return FALSE;
8198                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8199         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8200                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8201
8202                 if(nMine == 1) return FALSE; // bare King
8203                 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
8204                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8205                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8206                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8207                 if(pCnt[WhiteKnight+side])
8208                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8209                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8210                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8211                 if(nBishops)
8212                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8213                 if(pCnt[WhiteAlfil+side])
8214                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8215                 if(pCnt[WhiteWazir+side])
8216                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8217         }
8218
8219         return TRUE;
8220 }
8221
8222 int
8223 CompareWithRights (Board b1, Board b2)
8224 {
8225     int rights = 0;
8226     if(!CompareBoards(b1, b2)) return FALSE;
8227     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8228     /* compare castling rights */
8229     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8230            rights++; /* King lost rights, while rook still had them */
8231     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8232         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8233            rights++; /* but at least one rook lost them */
8234     }
8235     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8236            rights++;
8237     if( b1[CASTLING][5] != NoRights ) {
8238         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8239            rights++;
8240     }
8241     return rights == 0;
8242 }
8243
8244 int
8245 Adjudicate (ChessProgramState *cps)
8246 {       // [HGM] some adjudications useful with buggy engines
8247         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8248         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8249         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8250         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8251         int k, drop, count = 0; static int bare = 1;
8252         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8253         Boolean canAdjudicate = !appData.icsActive;
8254
8255         // most tests only when we understand the game, i.e. legality-checking on
8256             if( appData.testLegality )
8257             {   /* [HGM] Some more adjudications for obstinate engines */
8258                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8259                 static int moveCount = 6;
8260                 ChessMove result;
8261                 char *reason = NULL;
8262
8263                 /* Count what is on board. */
8264                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8265
8266                 /* Some material-based adjudications that have to be made before stalemate test */
8267                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8268                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8269                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8270                      if(canAdjudicate && appData.checkMates) {
8271                          if(engineOpponent)
8272                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8273                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8274                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8275                          return 1;
8276                      }
8277                 }
8278
8279                 /* Bare King in Shatranj (loses) or Losers (wins) */
8280                 if( nrW == 1 || nrB == 1) {
8281                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8282                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8283                      if(canAdjudicate && appData.checkMates) {
8284                          if(engineOpponent)
8285                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8286                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8287                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8288                          return 1;
8289                      }
8290                   } else
8291                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8292                   {    /* bare King */
8293                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8294                         if(canAdjudicate && appData.checkMates) {
8295                             /* but only adjudicate if adjudication enabled */
8296                             if(engineOpponent)
8297                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8298                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8299                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8300                             return 1;
8301                         }
8302                   }
8303                 } else bare = 1;
8304
8305
8306             // don't wait for engine to announce game end if we can judge ourselves
8307             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8308               case MT_CHECK:
8309                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8310                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8311                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8312                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8313                             checkCnt++;
8314                         if(checkCnt >= 2) {
8315                             reason = "Xboard adjudication: 3rd check";
8316                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8317                             break;
8318                         }
8319                     }
8320                 }
8321               case MT_NONE:
8322               default:
8323                 break;
8324               case MT_STEALMATE:
8325               case MT_STALEMATE:
8326               case MT_STAINMATE:
8327                 reason = "Xboard adjudication: Stalemate";
8328                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8329                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8330                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8331                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8332                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8333                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8334                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8335                                                                         EP_CHECKMATE : EP_WINS);
8336                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8337                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8338                 }
8339                 break;
8340               case MT_CHECKMATE:
8341                 reason = "Xboard adjudication: Checkmate";
8342                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8343                 if(gameInfo.variant == VariantShogi) {
8344                     if(forwardMostMove > backwardMostMove
8345                        && moveList[forwardMostMove-1][1] == '@'
8346                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8347                         reason = "XBoard adjudication: pawn-drop mate";
8348                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8349                     }
8350                 }
8351                 break;
8352             }
8353
8354                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8355                     case EP_STALEMATE:
8356                         result = GameIsDrawn; break;
8357                     case EP_CHECKMATE:
8358                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8359                     case EP_WINS:
8360                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8361                     default:
8362                         result = EndOfFile;
8363                 }
8364                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8365                     if(engineOpponent)
8366                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8367                     GameEnds( result, reason, GE_XBOARD );
8368                     return 1;
8369                 }
8370
8371                 /* Next absolutely insufficient mating material. */
8372                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8373                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8374                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8375
8376                      /* always flag draws, for judging claims */
8377                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8378
8379                      if(canAdjudicate && appData.materialDraws) {
8380                          /* but only adjudicate them if adjudication enabled */
8381                          if(engineOpponent) {
8382                            SendToProgram("force\n", engineOpponent); // suppress reply
8383                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8384                          }
8385                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8386                          return 1;
8387                      }
8388                 }
8389
8390                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8391                 if(gameInfo.variant == VariantXiangqi ?
8392                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8393                  : nrW + nrB == 4 &&
8394                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8395                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8396                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8397                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8398                    ) ) {
8399                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8400                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8401                           if(engineOpponent) {
8402                             SendToProgram("force\n", engineOpponent); // suppress reply
8403                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8404                           }
8405                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8406                           return 1;
8407                      }
8408                 } else moveCount = 6;
8409             }
8410
8411         // Repetition draws and 50-move rule can be applied independently of legality testing
8412
8413                 /* Check for rep-draws */
8414                 count = 0;
8415                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8416                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8417                 for(k = forwardMostMove-2;
8418                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8419                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8420                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8421                     k-=2)
8422                 {   int rights=0;
8423                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8424                         /* compare castling rights */
8425                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8426                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8427                                 rights++; /* King lost rights, while rook still had them */
8428                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8429                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8430                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8431                                    rights++; /* but at least one rook lost them */
8432                         }
8433                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8434                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8435                                 rights++;
8436                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8437                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8438                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8439                                    rights++;
8440                         }
8441                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8442                             && appData.drawRepeats > 1) {
8443                              /* adjudicate after user-specified nr of repeats */
8444                              int result = GameIsDrawn;
8445                              char *details = "XBoard adjudication: repetition draw";
8446                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8447                                 // [HGM] xiangqi: check for forbidden perpetuals
8448                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8449                                 for(m=forwardMostMove; m>k; m-=2) {
8450                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8451                                         ourPerpetual = 0; // the current mover did not always check
8452                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8453                                         hisPerpetual = 0; // the opponent did not always check
8454                                 }
8455                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8456                                                                         ourPerpetual, hisPerpetual);
8457                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8458                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8459                                     details = "Xboard adjudication: perpetual checking";
8460                                 } else
8461                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8462                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8463                                 } else
8464                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8465                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8466                                         result = BlackWins;
8467                                         details = "Xboard adjudication: repetition";
8468                                     }
8469                                 } else // it must be XQ
8470                                 // Now check for perpetual chases
8471                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8472                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8473                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8474                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8475                                         static char resdet[MSG_SIZ];
8476                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8477                                         details = resdet;
8478                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8479                                     } else
8480                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8481                                         break; // Abort repetition-checking loop.
8482                                 }
8483                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8484                              }
8485                              if(engineOpponent) {
8486                                SendToProgram("force\n", engineOpponent); // suppress reply
8487                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8488                              }
8489                              GameEnds( result, details, GE_XBOARD );
8490                              return 1;
8491                         }
8492                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8493                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8494                     }
8495                 }
8496
8497                 /* Now we test for 50-move draws. Determine ply count */
8498                 count = forwardMostMove;
8499                 /* look for last irreversble move */
8500                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8501                     count--;
8502                 /* if we hit starting position, add initial plies */
8503                 if( count == backwardMostMove )
8504                     count -= initialRulePlies;
8505                 count = forwardMostMove - count;
8506                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8507                         // adjust reversible move counter for checks in Xiangqi
8508                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8509                         if(i < backwardMostMove) i = backwardMostMove;
8510                         while(i <= forwardMostMove) {
8511                                 lastCheck = inCheck; // check evasion does not count
8512                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8513                                 if(inCheck || lastCheck) count--; // check does not count
8514                                 i++;
8515                         }
8516                 }
8517                 if( count >= 100)
8518                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8519                          /* this is used to judge if draw claims are legal */
8520                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8521                          if(engineOpponent) {
8522                            SendToProgram("force\n", engineOpponent); // suppress reply
8523                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8524                          }
8525                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8526                          return 1;
8527                 }
8528
8529                 /* if draw offer is pending, treat it as a draw claim
8530                  * when draw condition present, to allow engines a way to
8531                  * claim draws before making their move to avoid a race
8532                  * condition occurring after their move
8533                  */
8534                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8535                          char *p = NULL;
8536                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8537                              p = "Draw claim: 50-move rule";
8538                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8539                              p = "Draw claim: 3-fold repetition";
8540                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8541                              p = "Draw claim: insufficient mating material";
8542                          if( p != NULL && canAdjudicate) {
8543                              if(engineOpponent) {
8544                                SendToProgram("force\n", engineOpponent); // suppress reply
8545                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8546                              }
8547                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8548                              return 1;
8549                          }
8550                 }
8551
8552                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8553                     if(engineOpponent) {
8554                       SendToProgram("force\n", engineOpponent); // suppress reply
8555                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8556                     }
8557                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8558                     return 1;
8559                 }
8560         return 0;
8561 }
8562
8563 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8564 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8565 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8566
8567 static int
8568 BitbaseProbe ()
8569 {
8570     int pieces[10], squares[10], cnt=0, r, f, res;
8571     static int loaded;
8572     static PPROBE_EGBB probeBB;
8573     if(!appData.testLegality) return 10;
8574     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8575     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8576     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8577     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8578         ChessSquare piece = boards[forwardMostMove][r][f];
8579         int black = (piece >= BlackPawn);
8580         int type = piece - black*BlackPawn;
8581         if(piece == EmptySquare) continue;
8582         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8583         if(type == WhiteKing) type = WhiteQueen + 1;
8584         type = egbbCode[type];
8585         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8586         pieces[cnt] = type + black*6;
8587         if(++cnt > 5) return 11;
8588     }
8589     pieces[cnt] = squares[cnt] = 0;
8590     // probe EGBB
8591     if(loaded == 2) return 13; // loading failed before
8592     if(loaded == 0) {
8593         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8594         HMODULE lib;
8595         PLOAD_EGBB loadBB;
8596         loaded = 2; // prepare for failure
8597         if(!path) return 13; // no egbb installed
8598         strncpy(buf, path + 8, MSG_SIZ);
8599         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8600         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8601         lib = LoadLibrary(buf);
8602         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8603         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8604         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8605         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8606         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8607         loaded = 1; // success!
8608     }
8609     res = probeBB(forwardMostMove & 1, pieces, squares);
8610     return res > 0 ? 1 : res < 0 ? -1 : 0;
8611 }
8612
8613 char *
8614 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8615 {   // [HGM] book: this routine intercepts moves to simulate book replies
8616     char *bookHit = NULL;
8617
8618     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8619         char buf[MSG_SIZ];
8620         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8621         SendToProgram(buf, cps);
8622     }
8623     //first determine if the incoming move brings opponent into his book
8624     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8625         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8626     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8627     if(bookHit != NULL && !cps->bookSuspend) {
8628         // make sure opponent is not going to reply after receiving move to book position
8629         SendToProgram("force\n", cps);
8630         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8631     }
8632     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8633     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8634     // now arrange restart after book miss
8635     if(bookHit) {
8636         // after a book hit we never send 'go', and the code after the call to this routine
8637         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8638         char buf[MSG_SIZ], *move = bookHit;
8639         if(cps->useSAN) {
8640             int fromX, fromY, toX, toY;
8641             char promoChar;
8642             ChessMove moveType;
8643             move = buf + 30;
8644             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8645                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8646                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8647                                     PosFlags(forwardMostMove),
8648                                     fromY, fromX, toY, toX, promoChar, move);
8649             } else {
8650                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8651                 bookHit = NULL;
8652             }
8653         }
8654         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8655         SendToProgram(buf, cps);
8656         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8657     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8658         SendToProgram("go\n", cps);
8659         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8660     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8661         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8662             SendToProgram("go\n", cps);
8663         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8664     }
8665     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8666 }
8667
8668 int
8669 LoadError (char *errmess, ChessProgramState *cps)
8670 {   // unloads engine and switches back to -ncp mode if it was first
8671     if(cps->initDone) return FALSE;
8672     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8673     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8674     cps->pr = NoProc;
8675     if(cps == &first) {
8676         appData.noChessProgram = TRUE;
8677         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8678         gameMode = BeginningOfGame; ModeHighlight();
8679         SetNCPMode();
8680     }
8681     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8682     DisplayMessage("", ""); // erase waiting message
8683     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8684     return TRUE;
8685 }
8686
8687 char *savedMessage;
8688 ChessProgramState *savedState;
8689 void
8690 DeferredBookMove (void)
8691 {
8692         if(savedState->lastPing != savedState->lastPong)
8693                     ScheduleDelayedEvent(DeferredBookMove, 10);
8694         else
8695         HandleMachineMove(savedMessage, savedState);
8696 }
8697
8698 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8699 static ChessProgramState *stalledEngine;
8700 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8701
8702 void
8703 HandleMachineMove (char *message, ChessProgramState *cps)
8704 {
8705     static char firstLeg[20], legs;
8706     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8707     char realname[MSG_SIZ];
8708     int fromX, fromY, toX, toY;
8709     ChessMove moveType;
8710     char promoChar, roar;
8711     char *p, *pv=buf1;
8712     int oldError;
8713     char *bookHit;
8714
8715     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8716         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8717         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8718             DisplayError(_("Invalid pairing from pairing engine"), 0);
8719             return;
8720         }
8721         pairingReceived = 1;
8722         NextMatchGame();
8723         return; // Skim the pairing messages here.
8724     }
8725
8726     oldError = cps->userError; cps->userError = 0;
8727
8728 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8729     /*
8730      * Kludge to ignore BEL characters
8731      */
8732     while (*message == '\007') message++;
8733
8734     /*
8735      * [HGM] engine debug message: ignore lines starting with '#' character
8736      */
8737     if(cps->debug && *message == '#') return;
8738
8739     /*
8740      * Look for book output
8741      */
8742     if (cps == &first && bookRequested) {
8743         if (message[0] == '\t' || message[0] == ' ') {
8744             /* Part of the book output is here; append it */
8745             strcat(bookOutput, message);
8746             strcat(bookOutput, "  \n");
8747             return;
8748         } else if (bookOutput[0] != NULLCHAR) {
8749             /* All of book output has arrived; display it */
8750             char *p = bookOutput;
8751             while (*p != NULLCHAR) {
8752                 if (*p == '\t') *p = ' ';
8753                 p++;
8754             }
8755             DisplayInformation(bookOutput);
8756             bookRequested = FALSE;
8757             /* Fall through to parse the current output */
8758         }
8759     }
8760
8761     /*
8762      * Look for machine move.
8763      */
8764     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8765         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8766     {
8767         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8768             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8769             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8770             stalledEngine = cps;
8771             if(appData.ponderNextMove) { // bring opponent out of ponder
8772                 if(gameMode == TwoMachinesPlay) {
8773                     if(cps->other->pause)
8774                         PauseEngine(cps->other);
8775                     else
8776                         SendToProgram("easy\n", cps->other);
8777                 }
8778             }
8779             StopClocks();
8780             return;
8781         }
8782
8783       if(cps->usePing) {
8784
8785         /* This method is only useful on engines that support ping */
8786         if(abortEngineThink) {
8787             if (appData.debugMode) {
8788                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8789             }
8790             SendToProgram("undo\n", cps);
8791             return;
8792         }
8793
8794         if (cps->lastPing != cps->lastPong) {
8795             /* Extra move from before last new; ignore */
8796             if (appData.debugMode) {
8797                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8798             }
8799           return;
8800         }
8801
8802       } else {
8803
8804         int machineWhite = FALSE;
8805
8806         switch (gameMode) {
8807           case BeginningOfGame:
8808             /* Extra move from before last reset; ignore */
8809             if (appData.debugMode) {
8810                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8811             }
8812             return;
8813
8814           case EndOfGame:
8815           case IcsIdle:
8816           default:
8817             /* Extra move after we tried to stop.  The mode test is
8818                not a reliable way of detecting this problem, but it's
8819                the best we can do on engines that don't support ping.
8820             */
8821             if (appData.debugMode) {
8822                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8823                         cps->which, gameMode);
8824             }
8825             SendToProgram("undo\n", cps);
8826             return;
8827
8828           case MachinePlaysWhite:
8829           case IcsPlayingWhite:
8830             machineWhite = TRUE;
8831             break;
8832
8833           case MachinePlaysBlack:
8834           case IcsPlayingBlack:
8835             machineWhite = FALSE;
8836             break;
8837
8838           case TwoMachinesPlay:
8839             machineWhite = (cps->twoMachinesColor[0] == 'w');
8840             break;
8841         }
8842         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8843             if (appData.debugMode) {
8844                 fprintf(debugFP,
8845                         "Ignoring move out of turn by %s, gameMode %d"
8846                         ", forwardMost %d\n",
8847                         cps->which, gameMode, forwardMostMove);
8848             }
8849             return;
8850         }
8851       }
8852
8853         if(cps->alphaRank) AlphaRank(machineMove, 4);
8854
8855         // [HGM] lion: (some very limited) support for Alien protocol
8856         killX = killY = kill2X = kill2Y = -1;
8857         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8858             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8859             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8860             return;
8861         }
8862         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8863             char *q = strchr(p+1, ',');            // second comma?
8864             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8865             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8866             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8867         }
8868         if(firstLeg[0]) { // there was a previous leg;
8869             // only support case where same piece makes two step
8870             char buf[20], *p = machineMove+1, *q = buf+1, f;
8871             safeStrCpy(buf, machineMove, 20);
8872             while(isdigit(*q)) q++; // find start of to-square
8873             safeStrCpy(machineMove, firstLeg, 20);
8874             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8875             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8876             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8877             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8878             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8879             firstLeg[0] = NULLCHAR; legs = 0;
8880         }
8881
8882         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8883                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8884             /* Machine move could not be parsed; ignore it. */
8885           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8886                     machineMove, _(cps->which));
8887             DisplayMoveError(buf1);
8888             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8889                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8890             if (gameMode == TwoMachinesPlay) {
8891               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8892                        buf1, GE_XBOARD);
8893             }
8894             return;
8895         }
8896
8897         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8898         /* So we have to redo legality test with true e.p. status here,  */
8899         /* to make sure an illegal e.p. capture does not slip through,   */
8900         /* to cause a forfeit on a justified illegal-move complaint      */
8901         /* of the opponent.                                              */
8902         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8903            ChessMove moveType;
8904            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8905                              fromY, fromX, toY, toX, promoChar);
8906             if(moveType == IllegalMove) {
8907               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8908                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8909                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8910                            buf1, GE_XBOARD);
8911                 return;
8912            } else if(!appData.fischerCastling)
8913            /* [HGM] Kludge to handle engines that send FRC-style castling
8914               when they shouldn't (like TSCP-Gothic) */
8915            switch(moveType) {
8916              case WhiteASideCastleFR:
8917              case BlackASideCastleFR:
8918                toX+=2;
8919                currentMoveString[2]++;
8920                break;
8921              case WhiteHSideCastleFR:
8922              case BlackHSideCastleFR:
8923                toX--;
8924                currentMoveString[2]--;
8925                break;
8926              default: ; // nothing to do, but suppresses warning of pedantic compilers
8927            }
8928         }
8929         hintRequested = FALSE;
8930         lastHint[0] = NULLCHAR;
8931         bookRequested = FALSE;
8932         /* Program may be pondering now */
8933         cps->maybeThinking = TRUE;
8934         if (cps->sendTime == 2) cps->sendTime = 1;
8935         if (cps->offeredDraw) cps->offeredDraw--;
8936
8937         /* [AS] Save move info*/
8938         pvInfoList[ forwardMostMove ].score = programStats.score;
8939         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8940         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8941
8942         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8943
8944         /* Test suites abort the 'game' after one move */
8945         if(*appData.finger) {
8946            static FILE *f;
8947            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8948            if(!f) f = fopen(appData.finger, "w");
8949            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8950            else { DisplayFatalError("Bad output file", errno, 0); return; }
8951            free(fen);
8952            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8953         }
8954         if(appData.epd) {
8955            if(solvingTime >= 0) {
8956               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8957               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8958            } else {
8959               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8960               if(solvingTime == -2) second.matchWins++;
8961            }
8962            OutputKibitz(2, buf1);
8963            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8964         }
8965
8966         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8967         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8968             int count = 0;
8969
8970             while( count < adjudicateLossPlies ) {
8971                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8972
8973                 if( count & 1 ) {
8974                     score = -score; /* Flip score for winning side */
8975                 }
8976
8977                 if( score > appData.adjudicateLossThreshold ) {
8978                     break;
8979                 }
8980
8981                 count++;
8982             }
8983
8984             if( count >= adjudicateLossPlies ) {
8985                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8986
8987                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8988                     "Xboard adjudication",
8989                     GE_XBOARD );
8990
8991                 return;
8992             }
8993         }
8994
8995         if(Adjudicate(cps)) {
8996             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8997             return; // [HGM] adjudicate: for all automatic game ends
8998         }
8999
9000 #if ZIPPY
9001         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9002             first.initDone) {
9003           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9004                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9005                 SendToICS("draw ");
9006                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9007           }
9008           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9009           ics_user_moved = 1;
9010           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9011                 char buf[3*MSG_SIZ];
9012
9013                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9014                         programStats.score / 100.,
9015                         programStats.depth,
9016                         programStats.time / 100.,
9017                         (unsigned int)programStats.nodes,
9018                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9019                         programStats.movelist);
9020                 SendToICS(buf);
9021           }
9022         }
9023 #endif
9024
9025         /* [AS] Clear stats for next move */
9026         ClearProgramStats();
9027         thinkOutput[0] = NULLCHAR;
9028         hiddenThinkOutputState = 0;
9029
9030         bookHit = NULL;
9031         if (gameMode == TwoMachinesPlay) {
9032             /* [HGM] relaying draw offers moved to after reception of move */
9033             /* and interpreting offer as claim if it brings draw condition */
9034             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9035                 SendToProgram("draw\n", cps->other);
9036             }
9037             if (cps->other->sendTime) {
9038                 SendTimeRemaining(cps->other,
9039                                   cps->other->twoMachinesColor[0] == 'w');
9040             }
9041             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9042             if (firstMove && !bookHit) {
9043                 firstMove = FALSE;
9044                 if (cps->other->useColors) {
9045                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9046                 }
9047                 SendToProgram("go\n", cps->other);
9048             }
9049             cps->other->maybeThinking = TRUE;
9050         }
9051
9052         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9053
9054         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9055
9056         if (!pausing && appData.ringBellAfterMoves) {
9057             if(!roar) RingBell();
9058         }
9059
9060         /*
9061          * Reenable menu items that were disabled while
9062          * machine was thinking
9063          */
9064         if (gameMode != TwoMachinesPlay)
9065             SetUserThinkingEnables();
9066
9067         // [HGM] book: after book hit opponent has received move and is now in force mode
9068         // force the book reply into it, and then fake that it outputted this move by jumping
9069         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9070         if(bookHit) {
9071                 static char bookMove[MSG_SIZ]; // a bit generous?
9072
9073                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9074                 strcat(bookMove, bookHit);
9075                 message = bookMove;
9076                 cps = cps->other;
9077                 programStats.nodes = programStats.depth = programStats.time =
9078                 programStats.score = programStats.got_only_move = 0;
9079                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9080
9081                 if(cps->lastPing != cps->lastPong) {
9082                     savedMessage = message; // args for deferred call
9083                     savedState = cps;
9084                     ScheduleDelayedEvent(DeferredBookMove, 10);
9085                     return;
9086                 }
9087                 goto FakeBookMove;
9088         }
9089
9090         return;
9091     }
9092
9093     /* Set special modes for chess engines.  Later something general
9094      *  could be added here; for now there is just one kludge feature,
9095      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9096      *  when "xboard" is given as an interactive command.
9097      */
9098     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9099         cps->useSigint = FALSE;
9100         cps->useSigterm = FALSE;
9101     }
9102     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9103       ParseFeatures(message+8, cps);
9104       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9105     }
9106
9107     if (!strncmp(message, "setup ", 6) && 
9108         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9109           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9110                                         ) { // [HGM] allow first engine to define opening position
9111       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9112       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9113       *buf = NULLCHAR;
9114       if(sscanf(message, "setup (%s", buf) == 1) {
9115         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9116         ASSIGN(appData.pieceToCharTable, buf);
9117       }
9118       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9119       if(dummy >= 3) {
9120         while(message[s] && message[s++] != ' ');
9121         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9122            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9123             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9124             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9125           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9126           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9127           startedFromSetupPosition = FALSE;
9128         }
9129       }
9130       if(startedFromSetupPosition) return;
9131       ParseFEN(boards[0], &dummy, message+s, FALSE);
9132       DrawPosition(TRUE, boards[0]);
9133       CopyBoard(initialPosition, boards[0]);
9134       startedFromSetupPosition = TRUE;
9135       return;
9136     }
9137     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9138       ChessSquare piece = WhitePawn;
9139       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9140       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9141       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9142       piece += CharToPiece(ID & 255) - WhitePawn;
9143       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9144       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9145       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9146       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9147       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9148       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9149                                                && gameInfo.variant != VariantGreat
9150                                                && gameInfo.variant != VariantFairy    ) return;
9151       if(piece < EmptySquare) {
9152         pieceDefs = TRUE;
9153         ASSIGN(pieceDesc[piece], buf1);
9154         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9155       }
9156       return;
9157     }
9158     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9159       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9160       Sweep(0);
9161       return;
9162     }
9163     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9164      * want this, I was asked to put it in, and obliged.
9165      */
9166     if (!strncmp(message, "setboard ", 9)) {
9167         Board initial_position;
9168
9169         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9170
9171         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9172             DisplayError(_("Bad FEN received from engine"), 0);
9173             return ;
9174         } else {
9175            Reset(TRUE, FALSE);
9176            CopyBoard(boards[0], initial_position);
9177            initialRulePlies = FENrulePlies;
9178            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9179            else gameMode = MachinePlaysBlack;
9180            DrawPosition(FALSE, boards[currentMove]);
9181         }
9182         return;
9183     }
9184
9185     /*
9186      * Look for communication commands
9187      */
9188     if (!strncmp(message, "telluser ", 9)) {
9189         if(message[9] == '\\' && message[10] == '\\')
9190             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9191         PlayTellSound();
9192         DisplayNote(message + 9);
9193         return;
9194     }
9195     if (!strncmp(message, "tellusererror ", 14)) {
9196         cps->userError = 1;
9197         if(message[14] == '\\' && message[15] == '\\')
9198             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9199         PlayTellSound();
9200         DisplayError(message + 14, 0);
9201         return;
9202     }
9203     if (!strncmp(message, "tellopponent ", 13)) {
9204       if (appData.icsActive) {
9205         if (loggedOn) {
9206           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9207           SendToICS(buf1);
9208         }
9209       } else {
9210         DisplayNote(message + 13);
9211       }
9212       return;
9213     }
9214     if (!strncmp(message, "tellothers ", 11)) {
9215       if (appData.icsActive) {
9216         if (loggedOn) {
9217           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9218           SendToICS(buf1);
9219         }
9220       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9221       return;
9222     }
9223     if (!strncmp(message, "tellall ", 8)) {
9224       if (appData.icsActive) {
9225         if (loggedOn) {
9226           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9227           SendToICS(buf1);
9228         }
9229       } else {
9230         DisplayNote(message + 8);
9231       }
9232       return;
9233     }
9234     if (strncmp(message, "warning", 7) == 0) {
9235         /* Undocumented feature, use tellusererror in new code */
9236         DisplayError(message, 0);
9237         return;
9238     }
9239     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9240         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9241         strcat(realname, " query");
9242         AskQuestion(realname, buf2, buf1, cps->pr);
9243         return;
9244     }
9245     /* Commands from the engine directly to ICS.  We don't allow these to be
9246      *  sent until we are logged on. Crafty kibitzes have been known to
9247      *  interfere with the login process.
9248      */
9249     if (loggedOn) {
9250         if (!strncmp(message, "tellics ", 8)) {
9251             SendToICS(message + 8);
9252             SendToICS("\n");
9253             return;
9254         }
9255         if (!strncmp(message, "tellicsnoalias ", 15)) {
9256             SendToICS(ics_prefix);
9257             SendToICS(message + 15);
9258             SendToICS("\n");
9259             return;
9260         }
9261         /* The following are for backward compatibility only */
9262         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9263             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9264             SendToICS(ics_prefix);
9265             SendToICS(message);
9266             SendToICS("\n");
9267             return;
9268         }
9269     }
9270     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9271         if(initPing == cps->lastPong) {
9272             if(gameInfo.variant == VariantUnknown) {
9273                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9274                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9275                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9276             }
9277             initPing = -1;
9278         }
9279         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9280             abortEngineThink = FALSE;
9281             DisplayMessage("", "");
9282             ThawUI();
9283         }
9284         return;
9285     }
9286     if(!strncmp(message, "highlight ", 10)) {
9287         if(appData.testLegality && !*engineVariant && appData.markers) return;
9288         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9289         return;
9290     }
9291     if(!strncmp(message, "click ", 6)) {
9292         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9293         if(appData.testLegality || !appData.oneClick) return;
9294         sscanf(message+6, "%c%d%c", &f, &y, &c);
9295         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9296         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9297         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9298         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9299         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9300         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9301             LeftClick(Release, lastLeftX, lastLeftY);
9302         controlKey  = (c == ',');
9303         LeftClick(Press, x, y);
9304         LeftClick(Release, x, y);
9305         first.highlight = f;
9306         return;
9307     }
9308     /*
9309      * If the move is illegal, cancel it and redraw the board.
9310      * Also deal with other error cases.  Matching is rather loose
9311      * here to accommodate engines written before the spec.
9312      */
9313     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9314         strncmp(message, "Error", 5) == 0) {
9315         if (StrStr(message, "name") ||
9316             StrStr(message, "rating") || StrStr(message, "?") ||
9317             StrStr(message, "result") || StrStr(message, "board") ||
9318             StrStr(message, "bk") || StrStr(message, "computer") ||
9319             StrStr(message, "variant") || StrStr(message, "hint") ||
9320             StrStr(message, "random") || StrStr(message, "depth") ||
9321             StrStr(message, "accepted")) {
9322             return;
9323         }
9324         if (StrStr(message, "protover")) {
9325           /* Program is responding to input, so it's apparently done
9326              initializing, and this error message indicates it is
9327              protocol version 1.  So we don't need to wait any longer
9328              for it to initialize and send feature commands. */
9329           FeatureDone(cps, 1);
9330           cps->protocolVersion = 1;
9331           return;
9332         }
9333         cps->maybeThinking = FALSE;
9334
9335         if (StrStr(message, "draw")) {
9336             /* Program doesn't have "draw" command */
9337             cps->sendDrawOffers = 0;
9338             return;
9339         }
9340         if (cps->sendTime != 1 &&
9341             (StrStr(message, "time") || StrStr(message, "otim"))) {
9342           /* Program apparently doesn't have "time" or "otim" command */
9343           cps->sendTime = 0;
9344           return;
9345         }
9346         if (StrStr(message, "analyze")) {
9347             cps->analysisSupport = FALSE;
9348             cps->analyzing = FALSE;
9349 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9350             EditGameEvent(); // [HGM] try to preserve loaded game
9351             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9352             DisplayError(buf2, 0);
9353             return;
9354         }
9355         if (StrStr(message, "(no matching move)st")) {
9356           /* Special kludge for GNU Chess 4 only */
9357           cps->stKludge = TRUE;
9358           SendTimeControl(cps, movesPerSession, timeControl,
9359                           timeIncrement, appData.searchDepth,
9360                           searchTime);
9361           return;
9362         }
9363         if (StrStr(message, "(no matching move)sd")) {
9364           /* Special kludge for GNU Chess 4 only */
9365           cps->sdKludge = TRUE;
9366           SendTimeControl(cps, movesPerSession, timeControl,
9367                           timeIncrement, appData.searchDepth,
9368                           searchTime);
9369           return;
9370         }
9371         if (!StrStr(message, "llegal")) {
9372             return;
9373         }
9374         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9375             gameMode == IcsIdle) return;
9376         if (forwardMostMove <= backwardMostMove) return;
9377         if (pausing) PauseEvent();
9378       if(appData.forceIllegal) {
9379             // [HGM] illegal: machine refused move; force position after move into it
9380           SendToProgram("force\n", cps);
9381           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9382                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9383                 // when black is to move, while there might be nothing on a2 or black
9384                 // might already have the move. So send the board as if white has the move.
9385                 // But first we must change the stm of the engine, as it refused the last move
9386                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9387                 if(WhiteOnMove(forwardMostMove)) {
9388                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9389                     SendBoard(cps, forwardMostMove); // kludgeless board
9390                 } else {
9391                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9392                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9393                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9394                 }
9395           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9396             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9397                  gameMode == TwoMachinesPlay)
9398               SendToProgram("go\n", cps);
9399             return;
9400       } else
9401         if (gameMode == PlayFromGameFile) {
9402             /* Stop reading this game file */
9403             gameMode = EditGame;
9404             ModeHighlight();
9405         }
9406         /* [HGM] illegal-move claim should forfeit game when Xboard */
9407         /* only passes fully legal moves                            */
9408         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9409             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9410                                 "False illegal-move claim", GE_XBOARD );
9411             return; // do not take back move we tested as valid
9412         }
9413         currentMove = forwardMostMove-1;
9414         DisplayMove(currentMove-1); /* before DisplayMoveError */
9415         SwitchClocks(forwardMostMove-1); // [HGM] race
9416         DisplayBothClocks();
9417         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9418                 parseList[currentMove], _(cps->which));
9419         DisplayMoveError(buf1);
9420         DrawPosition(FALSE, boards[currentMove]);
9421
9422         SetUserThinkingEnables();
9423         return;
9424     }
9425     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9426         /* Program has a broken "time" command that
9427            outputs a string not ending in newline.
9428            Don't use it. */
9429         cps->sendTime = 0;
9430     }
9431     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9432         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9433             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9434     }
9435
9436     /*
9437      * If chess program startup fails, exit with an error message.
9438      * Attempts to recover here are futile. [HGM] Well, we try anyway
9439      */
9440     if ((StrStr(message, "unknown host") != NULL)
9441         || (StrStr(message, "No remote directory") != NULL)
9442         || (StrStr(message, "not found") != NULL)
9443         || (StrStr(message, "No such file") != NULL)
9444         || (StrStr(message, "can't alloc") != NULL)
9445         || (StrStr(message, "Permission denied") != NULL)) {
9446
9447         cps->maybeThinking = FALSE;
9448         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9449                 _(cps->which), cps->program, cps->host, message);
9450         RemoveInputSource(cps->isr);
9451         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9452             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9453             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9454         }
9455         return;
9456     }
9457
9458     /*
9459      * Look for hint output
9460      */
9461     if (sscanf(message, "Hint: %s", buf1) == 1) {
9462         if (cps == &first && hintRequested) {
9463             hintRequested = FALSE;
9464             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9465                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9466                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9467                                     PosFlags(forwardMostMove),
9468                                     fromY, fromX, toY, toX, promoChar, buf1);
9469                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9470                 DisplayInformation(buf2);
9471             } else {
9472                 /* Hint move could not be parsed!? */
9473               snprintf(buf2, sizeof(buf2),
9474                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9475                         buf1, _(cps->which));
9476                 DisplayError(buf2, 0);
9477             }
9478         } else {
9479           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9480         }
9481         return;
9482     }
9483
9484     /*
9485      * Ignore other messages if game is not in progress
9486      */
9487     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9488         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9489
9490     /*
9491      * look for win, lose, draw, or draw offer
9492      */
9493     if (strncmp(message, "1-0", 3) == 0) {
9494         char *p, *q, *r = "";
9495         p = strchr(message, '{');
9496         if (p) {
9497             q = strchr(p, '}');
9498             if (q) {
9499                 *q = NULLCHAR;
9500                 r = p + 1;
9501             }
9502         }
9503         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9504         return;
9505     } else if (strncmp(message, "0-1", 3) == 0) {
9506         char *p, *q, *r = "";
9507         p = strchr(message, '{');
9508         if (p) {
9509             q = strchr(p, '}');
9510             if (q) {
9511                 *q = NULLCHAR;
9512                 r = p + 1;
9513             }
9514         }
9515         /* Kludge for Arasan 4.1 bug */
9516         if (strcmp(r, "Black resigns") == 0) {
9517             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9518             return;
9519         }
9520         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9521         return;
9522     } else if (strncmp(message, "1/2", 3) == 0) {
9523         char *p, *q, *r = "";
9524         p = strchr(message, '{');
9525         if (p) {
9526             q = strchr(p, '}');
9527             if (q) {
9528                 *q = NULLCHAR;
9529                 r = p + 1;
9530             }
9531         }
9532
9533         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9534         return;
9535
9536     } else if (strncmp(message, "White resign", 12) == 0) {
9537         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9538         return;
9539     } else if (strncmp(message, "Black resign", 12) == 0) {
9540         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9541         return;
9542     } else if (strncmp(message, "White matches", 13) == 0 ||
9543                strncmp(message, "Black matches", 13) == 0   ) {
9544         /* [HGM] ignore GNUShogi noises */
9545         return;
9546     } else if (strncmp(message, "White", 5) == 0 &&
9547                message[5] != '(' &&
9548                StrStr(message, "Black") == NULL) {
9549         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9550         return;
9551     } else if (strncmp(message, "Black", 5) == 0 &&
9552                message[5] != '(') {
9553         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9554         return;
9555     } else if (strcmp(message, "resign") == 0 ||
9556                strcmp(message, "computer resigns") == 0) {
9557         switch (gameMode) {
9558           case MachinePlaysBlack:
9559           case IcsPlayingBlack:
9560             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9561             break;
9562           case MachinePlaysWhite:
9563           case IcsPlayingWhite:
9564             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9565             break;
9566           case TwoMachinesPlay:
9567             if (cps->twoMachinesColor[0] == 'w')
9568               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9569             else
9570               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9571             break;
9572           default:
9573             /* can't happen */
9574             break;
9575         }
9576         return;
9577     } else if (strncmp(message, "opponent mates", 14) == 0) {
9578         switch (gameMode) {
9579           case MachinePlaysBlack:
9580           case IcsPlayingBlack:
9581             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9582             break;
9583           case MachinePlaysWhite:
9584           case IcsPlayingWhite:
9585             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9586             break;
9587           case TwoMachinesPlay:
9588             if (cps->twoMachinesColor[0] == 'w')
9589               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9590             else
9591               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9592             break;
9593           default:
9594             /* can't happen */
9595             break;
9596         }
9597         return;
9598     } else if (strncmp(message, "computer mates", 14) == 0) {
9599         switch (gameMode) {
9600           case MachinePlaysBlack:
9601           case IcsPlayingBlack:
9602             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9603             break;
9604           case MachinePlaysWhite:
9605           case IcsPlayingWhite:
9606             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9607             break;
9608           case TwoMachinesPlay:
9609             if (cps->twoMachinesColor[0] == 'w')
9610               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9611             else
9612               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9613             break;
9614           default:
9615             /* can't happen */
9616             break;
9617         }
9618         return;
9619     } else if (strncmp(message, "checkmate", 9) == 0) {
9620         if (WhiteOnMove(forwardMostMove)) {
9621             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9622         } else {
9623             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9624         }
9625         return;
9626     } else if (strstr(message, "Draw") != NULL ||
9627                strstr(message, "game is a draw") != NULL) {
9628         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9629         return;
9630     } else if (strstr(message, "offer") != NULL &&
9631                strstr(message, "draw") != NULL) {
9632 #if ZIPPY
9633         if (appData.zippyPlay && first.initDone) {
9634             /* Relay offer to ICS */
9635             SendToICS(ics_prefix);
9636             SendToICS("draw\n");
9637         }
9638 #endif
9639         cps->offeredDraw = 2; /* valid until this engine moves twice */
9640         if (gameMode == TwoMachinesPlay) {
9641             if (cps->other->offeredDraw) {
9642                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9643             /* [HGM] in two-machine mode we delay relaying draw offer      */
9644             /* until after we also have move, to see if it is really claim */
9645             }
9646         } else if (gameMode == MachinePlaysWhite ||
9647                    gameMode == MachinePlaysBlack) {
9648           if (userOfferedDraw) {
9649             DisplayInformation(_("Machine accepts your draw offer"));
9650             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9651           } else {
9652             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9653           }
9654         }
9655     }
9656
9657
9658     /*
9659      * Look for thinking output
9660      */
9661     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9662           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9663                                 ) {
9664         int plylev, mvleft, mvtot, curscore, time;
9665         char mvname[MOVE_LEN];
9666         u64 nodes; // [DM]
9667         char plyext;
9668         int ignore = FALSE;
9669         int prefixHint = FALSE;
9670         mvname[0] = NULLCHAR;
9671
9672         switch (gameMode) {
9673           case MachinePlaysBlack:
9674           case IcsPlayingBlack:
9675             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9676             break;
9677           case MachinePlaysWhite:
9678           case IcsPlayingWhite:
9679             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9680             break;
9681           case AnalyzeMode:
9682           case AnalyzeFile:
9683             break;
9684           case IcsObserving: /* [DM] icsEngineAnalyze */
9685             if (!appData.icsEngineAnalyze) ignore = TRUE;
9686             break;
9687           case TwoMachinesPlay:
9688             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9689                 ignore = TRUE;
9690             }
9691             break;
9692           default:
9693             ignore = TRUE;
9694             break;
9695         }
9696
9697         if (!ignore) {
9698             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9699             int solved = 0;
9700             buf1[0] = NULLCHAR;
9701             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9702                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9703                 char score_buf[MSG_SIZ];
9704
9705                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9706                     nodes += u64Const(0x100000000);
9707
9708                 if (plyext != ' ' && plyext != '\t') {
9709                     time *= 100;
9710                 }
9711
9712                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9713                 if( cps->scoreIsAbsolute &&
9714                     ( gameMode == MachinePlaysBlack ||
9715                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9716                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9717                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9718                      !WhiteOnMove(currentMove)
9719                     ) )
9720                 {
9721                     curscore = -curscore;
9722                 }
9723
9724                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9725
9726                 if(*bestMove) { // rememer time best EPD move was first found
9727                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9728                     ChessMove mt; char *p = bestMove;
9729                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9730                     solved = 0;
9731                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9732                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9733                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9734                             solved = 1;
9735                             break;
9736                         }
9737                         while(*p && *p != ' ') p++;
9738                         while(*p == ' ') p++;
9739                     }
9740                     if(!solved) solvingTime = -1;
9741                 }
9742                 if(*avoidMove && !solved) {
9743                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9744                     ChessMove mt; char *p = avoidMove, solved = 1;
9745                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9746                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9747                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9748                             solved = 0; solvingTime = -2;
9749                             break;
9750                         }
9751                         while(*p && *p != ' ') p++;
9752                         while(*p == ' ') p++;
9753                     }
9754                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9755                 }
9756
9757                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9758                         char buf[MSG_SIZ];
9759                         FILE *f;
9760                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9761                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9762                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9763                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9764                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9765                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9766                                 fclose(f);
9767                         }
9768                         else
9769                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9770                           DisplayError(_("failed writing PV"), 0);
9771                 }
9772
9773                 tempStats.depth = plylev;
9774                 tempStats.nodes = nodes;
9775                 tempStats.time = time;
9776                 tempStats.score = curscore;
9777                 tempStats.got_only_move = 0;
9778
9779                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9780                         int ticklen;
9781
9782                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9783                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9784                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9785                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9786                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9787                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9788                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9789                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9790                 }
9791
9792                 /* Buffer overflow protection */
9793                 if (pv[0] != NULLCHAR) {
9794                     if (strlen(pv) >= sizeof(tempStats.movelist)
9795                         && appData.debugMode) {
9796                         fprintf(debugFP,
9797                                 "PV is too long; using the first %u bytes.\n",
9798                                 (unsigned) sizeof(tempStats.movelist) - 1);
9799                     }
9800
9801                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9802                 } else {
9803                     sprintf(tempStats.movelist, " no PV\n");
9804                 }
9805
9806                 if (tempStats.seen_stat) {
9807                     tempStats.ok_to_send = 1;
9808                 }
9809
9810                 if (strchr(tempStats.movelist, '(') != NULL) {
9811                     tempStats.line_is_book = 1;
9812                     tempStats.nr_moves = 0;
9813                     tempStats.moves_left = 0;
9814                 } else {
9815                     tempStats.line_is_book = 0;
9816                 }
9817
9818                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9819                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9820
9821                 SendProgramStatsToFrontend( cps, &tempStats );
9822
9823                 /*
9824                     [AS] Protect the thinkOutput buffer from overflow... this
9825                     is only useful if buf1 hasn't overflowed first!
9826                 */
9827                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9828                 if(curscore >= MATE_SCORE) 
9829                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9830                 else if(curscore <= -MATE_SCORE) 
9831                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9832                 else
9833                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9834                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9835                          plylev,
9836                          (gameMode == TwoMachinesPlay ?
9837                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9838                          score_buf,
9839                          prefixHint ? lastHint : "",
9840                          prefixHint ? " " : "" );
9841
9842                 if( buf1[0] != NULLCHAR ) {
9843                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9844
9845                     if( strlen(pv) > max_len ) {
9846                         if( appData.debugMode) {
9847                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9848                         }
9849                         pv[max_len+1] = '\0';
9850                     }
9851
9852                     strcat( thinkOutput, pv);
9853                 }
9854
9855                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9856                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9857                     DisplayMove(currentMove - 1);
9858                 }
9859                 return;
9860
9861             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9862                 /* crafty (9.25+) says "(only move) <move>"
9863                  * if there is only 1 legal move
9864                  */
9865                 sscanf(p, "(only move) %s", buf1);
9866                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9867                 sprintf(programStats.movelist, "%s (only move)", buf1);
9868                 programStats.depth = 1;
9869                 programStats.nr_moves = 1;
9870                 programStats.moves_left = 1;
9871                 programStats.nodes = 1;
9872                 programStats.time = 1;
9873                 programStats.got_only_move = 1;
9874
9875                 /* Not really, but we also use this member to
9876                    mean "line isn't going to change" (Crafty
9877                    isn't searching, so stats won't change) */
9878                 programStats.line_is_book = 1;
9879
9880                 SendProgramStatsToFrontend( cps, &programStats );
9881
9882                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9883                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9884                     DisplayMove(currentMove - 1);
9885                 }
9886                 return;
9887             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9888                               &time, &nodes, &plylev, &mvleft,
9889                               &mvtot, mvname) >= 5) {
9890                 /* The stat01: line is from Crafty (9.29+) in response
9891                    to the "." command */
9892                 programStats.seen_stat = 1;
9893                 cps->maybeThinking = TRUE;
9894
9895                 if (programStats.got_only_move || !appData.periodicUpdates)
9896                   return;
9897
9898                 programStats.depth = plylev;
9899                 programStats.time = time;
9900                 programStats.nodes = nodes;
9901                 programStats.moves_left = mvleft;
9902                 programStats.nr_moves = mvtot;
9903                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9904                 programStats.ok_to_send = 1;
9905                 programStats.movelist[0] = '\0';
9906
9907                 SendProgramStatsToFrontend( cps, &programStats );
9908
9909                 return;
9910
9911             } else if (strncmp(message,"++",2) == 0) {
9912                 /* Crafty 9.29+ outputs this */
9913                 programStats.got_fail = 2;
9914                 return;
9915
9916             } else if (strncmp(message,"--",2) == 0) {
9917                 /* Crafty 9.29+ outputs this */
9918                 programStats.got_fail = 1;
9919                 return;
9920
9921             } else if (thinkOutput[0] != NULLCHAR &&
9922                        strncmp(message, "    ", 4) == 0) {
9923                 unsigned message_len;
9924
9925                 p = message;
9926                 while (*p && *p == ' ') p++;
9927
9928                 message_len = strlen( p );
9929
9930                 /* [AS] Avoid buffer overflow */
9931                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9932                     strcat(thinkOutput, " ");
9933                     strcat(thinkOutput, p);
9934                 }
9935
9936                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9937                     strcat(programStats.movelist, " ");
9938                     strcat(programStats.movelist, p);
9939                 }
9940
9941                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9942                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9943                     DisplayMove(currentMove - 1);
9944                 }
9945                 return;
9946             }
9947         }
9948         else {
9949             buf1[0] = NULLCHAR;
9950
9951             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9952                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9953             {
9954                 ChessProgramStats cpstats;
9955
9956                 if (plyext != ' ' && plyext != '\t') {
9957                     time *= 100;
9958                 }
9959
9960                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9961                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9962                     curscore = -curscore;
9963                 }
9964
9965                 cpstats.depth = plylev;
9966                 cpstats.nodes = nodes;
9967                 cpstats.time = time;
9968                 cpstats.score = curscore;
9969                 cpstats.got_only_move = 0;
9970                 cpstats.movelist[0] = '\0';
9971
9972                 if (buf1[0] != NULLCHAR) {
9973                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9974                 }
9975
9976                 cpstats.ok_to_send = 0;
9977                 cpstats.line_is_book = 0;
9978                 cpstats.nr_moves = 0;
9979                 cpstats.moves_left = 0;
9980
9981                 SendProgramStatsToFrontend( cps, &cpstats );
9982             }
9983         }
9984     }
9985 }
9986
9987
9988 /* Parse a game score from the character string "game", and
9989    record it as the history of the current game.  The game
9990    score is NOT assumed to start from the standard position.
9991    The display is not updated in any way.
9992    */
9993 void
9994 ParseGameHistory (char *game)
9995 {
9996     ChessMove moveType;
9997     int fromX, fromY, toX, toY, boardIndex;
9998     char promoChar;
9999     char *p, *q;
10000     char buf[MSG_SIZ];
10001
10002     if (appData.debugMode)
10003       fprintf(debugFP, "Parsing game history: %s\n", game);
10004
10005     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10006     gameInfo.site = StrSave(appData.icsHost);
10007     gameInfo.date = PGNDate();
10008     gameInfo.round = StrSave("-");
10009
10010     /* Parse out names of players */
10011     while (*game == ' ') game++;
10012     p = buf;
10013     while (*game != ' ') *p++ = *game++;
10014     *p = NULLCHAR;
10015     gameInfo.white = StrSave(buf);
10016     while (*game == ' ') game++;
10017     p = buf;
10018     while (*game != ' ' && *game != '\n') *p++ = *game++;
10019     *p = NULLCHAR;
10020     gameInfo.black = StrSave(buf);
10021
10022     /* Parse moves */
10023     boardIndex = blackPlaysFirst ? 1 : 0;
10024     yynewstr(game);
10025     for (;;) {
10026         yyboardindex = boardIndex;
10027         moveType = (ChessMove) Myylex();
10028         switch (moveType) {
10029           case IllegalMove:             /* maybe suicide chess, etc. */
10030   if (appData.debugMode) {
10031     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10032     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10033     setbuf(debugFP, NULL);
10034   }
10035           case WhitePromotion:
10036           case BlackPromotion:
10037           case WhiteNonPromotion:
10038           case BlackNonPromotion:
10039           case NormalMove:
10040           case FirstLeg:
10041           case WhiteCapturesEnPassant:
10042           case BlackCapturesEnPassant:
10043           case WhiteKingSideCastle:
10044           case WhiteQueenSideCastle:
10045           case BlackKingSideCastle:
10046           case BlackQueenSideCastle:
10047           case WhiteKingSideCastleWild:
10048           case WhiteQueenSideCastleWild:
10049           case BlackKingSideCastleWild:
10050           case BlackQueenSideCastleWild:
10051           /* PUSH Fabien */
10052           case WhiteHSideCastleFR:
10053           case WhiteASideCastleFR:
10054           case BlackHSideCastleFR:
10055           case BlackASideCastleFR:
10056           /* POP Fabien */
10057             fromX = currentMoveString[0] - AAA;
10058             fromY = currentMoveString[1] - ONE;
10059             toX = currentMoveString[2] - AAA;
10060             toY = currentMoveString[3] - ONE;
10061             promoChar = currentMoveString[4];
10062             break;
10063           case WhiteDrop:
10064           case BlackDrop:
10065             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10066             fromX = moveType == WhiteDrop ?
10067               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10068             (int) CharToPiece(ToLower(currentMoveString[0]));
10069             fromY = DROP_RANK;
10070             toX = currentMoveString[2] - AAA;
10071             toY = currentMoveString[3] - ONE;
10072             promoChar = NULLCHAR;
10073             break;
10074           case AmbiguousMove:
10075             /* bug? */
10076             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10077   if (appData.debugMode) {
10078     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10079     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10080     setbuf(debugFP, NULL);
10081   }
10082             DisplayError(buf, 0);
10083             return;
10084           case ImpossibleMove:
10085             /* bug? */
10086             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10087   if (appData.debugMode) {
10088     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10089     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10090     setbuf(debugFP, NULL);
10091   }
10092             DisplayError(buf, 0);
10093             return;
10094           case EndOfFile:
10095             if (boardIndex < backwardMostMove) {
10096                 /* Oops, gap.  How did that happen? */
10097                 DisplayError(_("Gap in move list"), 0);
10098                 return;
10099             }
10100             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10101             if (boardIndex > forwardMostMove) {
10102                 forwardMostMove = boardIndex;
10103             }
10104             return;
10105           case ElapsedTime:
10106             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10107                 strcat(parseList[boardIndex-1], " ");
10108                 strcat(parseList[boardIndex-1], yy_text);
10109             }
10110             continue;
10111           case Comment:
10112           case PGNTag:
10113           case NAG:
10114           default:
10115             /* ignore */
10116             continue;
10117           case WhiteWins:
10118           case BlackWins:
10119           case GameIsDrawn:
10120           case GameUnfinished:
10121             if (gameMode == IcsExamining) {
10122                 if (boardIndex < backwardMostMove) {
10123                     /* Oops, gap.  How did that happen? */
10124                     return;
10125                 }
10126                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10127                 return;
10128             }
10129             gameInfo.result = moveType;
10130             p = strchr(yy_text, '{');
10131             if (p == NULL) p = strchr(yy_text, '(');
10132             if (p == NULL) {
10133                 p = yy_text;
10134                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10135             } else {
10136                 q = strchr(p, *p == '{' ? '}' : ')');
10137                 if (q != NULL) *q = NULLCHAR;
10138                 p++;
10139             }
10140             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10141             gameInfo.resultDetails = StrSave(p);
10142             continue;
10143         }
10144         if (boardIndex >= forwardMostMove &&
10145             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10146             backwardMostMove = blackPlaysFirst ? 1 : 0;
10147             return;
10148         }
10149         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10150                                  fromY, fromX, toY, toX, promoChar,
10151                                  parseList[boardIndex]);
10152         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10153         /* currentMoveString is set as a side-effect of yylex */
10154         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10155         strcat(moveList[boardIndex], "\n");
10156         boardIndex++;
10157         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10158         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10159           case MT_NONE:
10160           case MT_STALEMATE:
10161           default:
10162             break;
10163           case MT_CHECK:
10164             if(!IS_SHOGI(gameInfo.variant))
10165                 strcat(parseList[boardIndex - 1], "+");
10166             break;
10167           case MT_CHECKMATE:
10168           case MT_STAINMATE:
10169             strcat(parseList[boardIndex - 1], "#");
10170             break;
10171         }
10172     }
10173 }
10174
10175
10176 /* Apply a move to the given board  */
10177 void
10178 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10179 {
10180   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10181   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10182
10183     /* [HGM] compute & store e.p. status and castling rights for new position */
10184     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10185
10186       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10187       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10188       board[EP_STATUS] = EP_NONE;
10189       board[EP_FILE] = board[EP_RANK] = 100;
10190
10191   if (fromY == DROP_RANK) {
10192         /* must be first */
10193         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10194             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10195             return;
10196         }
10197         piece = board[toY][toX] = (ChessSquare) fromX;
10198   } else {
10199 //      ChessSquare victim;
10200       int i;
10201
10202       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10203 //           victim = board[killY][killX],
10204            killed = board[killY][killX],
10205            board[killY][killX] = EmptySquare,
10206            board[EP_STATUS] = EP_CAPTURE;
10207            if( kill2X >= 0 && kill2Y >= 0)
10208              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10209       }
10210
10211       if( board[toY][toX] != EmptySquare ) {
10212            board[EP_STATUS] = EP_CAPTURE;
10213            if( (fromX != toX || fromY != toY) && // not igui!
10214                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10215                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10216                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10217            }
10218       }
10219
10220       pawn = board[fromY][fromX];
10221       if( pawn == WhiteLance || pawn == BlackLance ) {
10222            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10223                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10224                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10225            }
10226       }
10227       if( pawn == WhitePawn ) {
10228            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10229                board[EP_STATUS] = EP_PAWN_MOVE;
10230            if( toY-fromY>=2) {
10231                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10232                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10233                         gameInfo.variant != VariantBerolina || toX < fromX)
10234                       board[EP_STATUS] = toX | berolina;
10235                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10236                         gameInfo.variant != VariantBerolina || toX > fromX)
10237                       board[EP_STATUS] = toX;
10238            }
10239       } else
10240       if( pawn == BlackPawn ) {
10241            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10242                board[EP_STATUS] = EP_PAWN_MOVE;
10243            if( toY-fromY<= -2) {
10244                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10245                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10246                         gameInfo.variant != VariantBerolina || toX < fromX)
10247                       board[EP_STATUS] = toX | berolina;
10248                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10249                         gameInfo.variant != VariantBerolina || toX > fromX)
10250                       board[EP_STATUS] = toX;
10251            }
10252        }
10253
10254        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10255        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10256        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10257        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10258
10259        for(i=0; i<nrCastlingRights; i++) {
10260            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10261               board[CASTLING][i] == toX   && castlingRank[i] == toY
10262              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10263        }
10264
10265        if(gameInfo.variant == VariantSChess) { // update virginity
10266            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10267            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10268            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10269            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10270        }
10271
10272      if (fromX == toX && fromY == toY && killX < 0) return;
10273
10274      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10275      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10276      if(gameInfo.variant == VariantKnightmate)
10277          king += (int) WhiteUnicorn - (int) WhiteKing;
10278
10279     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10280        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10281         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10282         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10283         board[EP_STATUS] = EP_NONE; // capture was fake!
10284     } else
10285     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10286         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10287         board[toY][toX] = piece;
10288         board[EP_STATUS] = EP_NONE; // capture was fake!
10289     } else
10290     /* Code added by Tord: */
10291     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10292     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10293         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10294       board[EP_STATUS] = EP_NONE; // capture was fake!
10295       board[fromY][fromX] = EmptySquare;
10296       board[toY][toX] = EmptySquare;
10297       if((toX > fromX) != (piece == WhiteRook)) {
10298         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10299       } else {
10300         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10301       }
10302     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10303                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10304       board[EP_STATUS] = EP_NONE;
10305       board[fromY][fromX] = EmptySquare;
10306       board[toY][toX] = EmptySquare;
10307       if((toX > fromX) != (piece == BlackRook)) {
10308         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10309       } else {
10310         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10311       }
10312     /* End of code added by Tord */
10313
10314     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10315         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10316         board[toY][toX] = piece;
10317     } else if (board[fromY][fromX] == king
10318         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10319         && toY == fromY && toX > fromX+1) {
10320         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10321         board[fromY][toX-1] = board[fromY][rookX];
10322         board[fromY][rookX] = EmptySquare;
10323         board[fromY][fromX] = EmptySquare;
10324         board[toY][toX] = king;
10325     } else if (board[fromY][fromX] == king
10326         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10327                && toY == fromY && toX < fromX-1) {
10328         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10329         board[fromY][toX+1] = board[fromY][rookX];
10330         board[fromY][rookX] = EmptySquare;
10331         board[fromY][fromX] = EmptySquare;
10332         board[toY][toX] = king;
10333     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10334                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10335                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10336                ) {
10337         /* white pawn promotion */
10338         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10339         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10340             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10341         board[fromY][fromX] = EmptySquare;
10342     } else if ((fromY >= BOARD_HEIGHT>>1)
10343                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10344                && (toX != fromX)
10345                && gameInfo.variant != VariantXiangqi
10346                && gameInfo.variant != VariantBerolina
10347                && (pawn == WhitePawn)
10348                && (board[toY][toX] == EmptySquare)) {
10349         board[fromY][fromX] = EmptySquare;
10350         board[toY][toX] = piece;
10351         if(toY == epRank - 128 + 1)
10352             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10353         else
10354             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10355     } else if ((fromY == BOARD_HEIGHT-4)
10356                && (toX == fromX)
10357                && gameInfo.variant == VariantBerolina
10358                && (board[fromY][fromX] == WhitePawn)
10359                && (board[toY][toX] == EmptySquare)) {
10360         board[fromY][fromX] = EmptySquare;
10361         board[toY][toX] = WhitePawn;
10362         if(oldEP & EP_BEROLIN_A) {
10363                 captured = board[fromY][fromX-1];
10364                 board[fromY][fromX-1] = EmptySquare;
10365         }else{  captured = board[fromY][fromX+1];
10366                 board[fromY][fromX+1] = EmptySquare;
10367         }
10368     } else if (board[fromY][fromX] == king
10369         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10370                && toY == fromY && toX > fromX+1) {
10371         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10372         board[fromY][toX-1] = board[fromY][rookX];
10373         board[fromY][rookX] = EmptySquare;
10374         board[fromY][fromX] = EmptySquare;
10375         board[toY][toX] = king;
10376     } else if (board[fromY][fromX] == king
10377         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10378                && toY == fromY && toX < fromX-1) {
10379         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10380         board[fromY][toX+1] = board[fromY][rookX];
10381         board[fromY][rookX] = EmptySquare;
10382         board[fromY][fromX] = EmptySquare;
10383         board[toY][toX] = king;
10384     } else if (fromY == 7 && fromX == 3
10385                && board[fromY][fromX] == BlackKing
10386                && toY == 7 && toX == 5) {
10387         board[fromY][fromX] = EmptySquare;
10388         board[toY][toX] = BlackKing;
10389         board[fromY][7] = EmptySquare;
10390         board[toY][4] = BlackRook;
10391     } else if (fromY == 7 && fromX == 3
10392                && board[fromY][fromX] == BlackKing
10393                && toY == 7 && toX == 1) {
10394         board[fromY][fromX] = EmptySquare;
10395         board[toY][toX] = BlackKing;
10396         board[fromY][0] = EmptySquare;
10397         board[toY][2] = BlackRook;
10398     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10399                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10400                && toY < promoRank && promoChar
10401                ) {
10402         /* black pawn promotion */
10403         board[toY][toX] = CharToPiece(ToLower(promoChar));
10404         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10405             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10406         board[fromY][fromX] = EmptySquare;
10407     } else if ((fromY < BOARD_HEIGHT>>1)
10408                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10409                && (toX != fromX)
10410                && gameInfo.variant != VariantXiangqi
10411                && gameInfo.variant != VariantBerolina
10412                && (pawn == BlackPawn)
10413                && (board[toY][toX] == EmptySquare)) {
10414         board[fromY][fromX] = EmptySquare;
10415         board[toY][toX] = piece;
10416         if(toY == epRank - 128 - 1)
10417             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10418         else
10419             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10420     } else if ((fromY == 3)
10421                && (toX == fromX)
10422                && gameInfo.variant == VariantBerolina
10423                && (board[fromY][fromX] == BlackPawn)
10424                && (board[toY][toX] == EmptySquare)) {
10425         board[fromY][fromX] = EmptySquare;
10426         board[toY][toX] = BlackPawn;
10427         if(oldEP & EP_BEROLIN_A) {
10428                 captured = board[fromY][fromX-1];
10429                 board[fromY][fromX-1] = EmptySquare;
10430         }else{  captured = board[fromY][fromX+1];
10431                 board[fromY][fromX+1] = EmptySquare;
10432         }
10433     } else {
10434         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10435         board[fromY][fromX] = EmptySquare;
10436         board[toY][toX] = piece;
10437     }
10438   }
10439
10440     if (gameInfo.holdingsWidth != 0) {
10441
10442       /* !!A lot more code needs to be written to support holdings  */
10443       /* [HGM] OK, so I have written it. Holdings are stored in the */
10444       /* penultimate board files, so they are automaticlly stored   */
10445       /* in the game history.                                       */
10446       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10447                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10448         /* Delete from holdings, by decreasing count */
10449         /* and erasing image if necessary            */
10450         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10451         if(p < (int) BlackPawn) { /* white drop */
10452              p -= (int)WhitePawn;
10453                  p = PieceToNumber((ChessSquare)p);
10454              if(p >= gameInfo.holdingsSize) p = 0;
10455              if(--board[p][BOARD_WIDTH-2] <= 0)
10456                   board[p][BOARD_WIDTH-1] = EmptySquare;
10457              if((int)board[p][BOARD_WIDTH-2] < 0)
10458                         board[p][BOARD_WIDTH-2] = 0;
10459         } else {                  /* black drop */
10460              p -= (int)BlackPawn;
10461                  p = PieceToNumber((ChessSquare)p);
10462              if(p >= gameInfo.holdingsSize) p = 0;
10463              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10464                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10465              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10466                         board[BOARD_HEIGHT-1-p][1] = 0;
10467         }
10468       }
10469       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10470           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10471         /* [HGM] holdings: Add to holdings, if holdings exist */
10472         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10473                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10474                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10475         }
10476         p = (int) captured;
10477         if (p >= (int) BlackPawn) {
10478           p -= (int)BlackPawn;
10479           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10480                   /* Restore shogi-promoted piece to its original  first */
10481                   captured = (ChessSquare) (DEMOTED(captured));
10482                   p = DEMOTED(p);
10483           }
10484           p = PieceToNumber((ChessSquare)p);
10485           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10486           board[p][BOARD_WIDTH-2]++;
10487           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10488         } else {
10489           p -= (int)WhitePawn;
10490           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10491                   captured = (ChessSquare) (DEMOTED(captured));
10492                   p = DEMOTED(p);
10493           }
10494           p = PieceToNumber((ChessSquare)p);
10495           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10496           board[BOARD_HEIGHT-1-p][1]++;
10497           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10498         }
10499       }
10500     } else if (gameInfo.variant == VariantAtomic) {
10501       if (captured != EmptySquare) {
10502         int y, x;
10503         for (y = toY-1; y <= toY+1; y++) {
10504           for (x = toX-1; x <= toX+1; x++) {
10505             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10506                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10507               board[y][x] = EmptySquare;
10508             }
10509           }
10510         }
10511         board[toY][toX] = EmptySquare;
10512       }
10513     }
10514
10515     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10516         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10517     } else
10518     if(promoChar == '+') {
10519         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10520         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10521         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10522           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10523     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10524         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10525         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10526            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10527         board[toY][toX] = newPiece;
10528     }
10529     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10530                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10531         // [HGM] superchess: take promotion piece out of holdings
10532         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10533         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10534             if(!--board[k][BOARD_WIDTH-2])
10535                 board[k][BOARD_WIDTH-1] = EmptySquare;
10536         } else {
10537             if(!--board[BOARD_HEIGHT-1-k][1])
10538                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10539         }
10540     }
10541 }
10542
10543 /* Updates forwardMostMove */
10544 void
10545 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10546 {
10547     int x = toX, y = toY;
10548     char *s = parseList[forwardMostMove];
10549     ChessSquare p = boards[forwardMostMove][toY][toX];
10550 //    forwardMostMove++; // [HGM] bare: moved downstream
10551
10552     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10553     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10554     (void) CoordsToAlgebraic(boards[forwardMostMove],
10555                              PosFlags(forwardMostMove),
10556                              fromY, fromX, y, x, (killX < 0)*promoChar,
10557                              s);
10558     if(kill2X >= 0 && kill2Y >= 0)
10559         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10560     if(killX >= 0 && killY >= 0)
10561         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10562                                            toX + AAA, toY + ONE - '0', promoChar);
10563
10564     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10565         int timeLeft; static int lastLoadFlag=0; int king, piece;
10566         piece = boards[forwardMostMove][fromY][fromX];
10567         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10568         if(gameInfo.variant == VariantKnightmate)
10569             king += (int) WhiteUnicorn - (int) WhiteKing;
10570         if(forwardMostMove == 0) {
10571             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10572                 fprintf(serverMoves, "%s;", UserName());
10573             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10574                 fprintf(serverMoves, "%s;", second.tidy);
10575             fprintf(serverMoves, "%s;", first.tidy);
10576             if(gameMode == MachinePlaysWhite)
10577                 fprintf(serverMoves, "%s;", UserName());
10578             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10579                 fprintf(serverMoves, "%s;", second.tidy);
10580         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10581         lastLoadFlag = loadFlag;
10582         // print base move
10583         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10584         // print castling suffix
10585         if( toY == fromY && piece == king ) {
10586             if(toX-fromX > 1)
10587                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10588             if(fromX-toX >1)
10589                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10590         }
10591         // e.p. suffix
10592         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10593              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10594              boards[forwardMostMove][toY][toX] == EmptySquare
10595              && fromX != toX && fromY != toY)
10596                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10597         // promotion suffix
10598         if(promoChar != NULLCHAR) {
10599             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10600                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10601                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10602             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10603         }
10604         if(!loadFlag) {
10605                 char buf[MOVE_LEN*2], *p; int len;
10606             fprintf(serverMoves, "/%d/%d",
10607                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10608             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10609             else                      timeLeft = blackTimeRemaining/1000;
10610             fprintf(serverMoves, "/%d", timeLeft);
10611                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10612                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10613                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10614                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10615             fprintf(serverMoves, "/%s", buf);
10616         }
10617         fflush(serverMoves);
10618     }
10619
10620     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10621         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10622       return;
10623     }
10624     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10625     if (commentList[forwardMostMove+1] != NULL) {
10626         free(commentList[forwardMostMove+1]);
10627         commentList[forwardMostMove+1] = NULL;
10628     }
10629     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10630     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10631     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10632     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10633     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10634     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10635     adjustedClock = FALSE;
10636     gameInfo.result = GameUnfinished;
10637     if (gameInfo.resultDetails != NULL) {
10638         free(gameInfo.resultDetails);
10639         gameInfo.resultDetails = NULL;
10640     }
10641     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10642                               moveList[forwardMostMove - 1]);
10643     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10644       case MT_NONE:
10645       case MT_STALEMATE:
10646       default:
10647         break;
10648       case MT_CHECK:
10649         if(!IS_SHOGI(gameInfo.variant))
10650             strcat(parseList[forwardMostMove - 1], "+");
10651         break;
10652       case MT_CHECKMATE:
10653       case MT_STAINMATE:
10654         strcat(parseList[forwardMostMove - 1], "#");
10655         break;
10656     }
10657 }
10658
10659 /* Updates currentMove if not pausing */
10660 void
10661 ShowMove (int fromX, int fromY, int toX, int toY)
10662 {
10663     int instant = (gameMode == PlayFromGameFile) ?
10664         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10665     if(appData.noGUI) return;
10666     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10667         if (!instant) {
10668             if (forwardMostMove == currentMove + 1) {
10669                 AnimateMove(boards[forwardMostMove - 1],
10670                             fromX, fromY, toX, toY);
10671             }
10672         }
10673         currentMove = forwardMostMove;
10674     }
10675
10676     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10677
10678     if (instant) return;
10679
10680     DisplayMove(currentMove - 1);
10681     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10682             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10683                 SetHighlights(fromX, fromY, toX, toY);
10684             }
10685     }
10686     DrawPosition(FALSE, boards[currentMove]);
10687     DisplayBothClocks();
10688     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10689 }
10690
10691 void
10692 SendEgtPath (ChessProgramState *cps)
10693 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10694         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10695
10696         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10697
10698         while(*p) {
10699             char c, *q = name+1, *r, *s;
10700
10701             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10702             while(*p && *p != ',') *q++ = *p++;
10703             *q++ = ':'; *q = 0;
10704             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10705                 strcmp(name, ",nalimov:") == 0 ) {
10706                 // take nalimov path from the menu-changeable option first, if it is defined
10707               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10708                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10709             } else
10710             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10711                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10712                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10713                 s = r = StrStr(s, ":") + 1; // beginning of path info
10714                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10715                 c = *r; *r = 0;             // temporarily null-terminate path info
10716                     *--q = 0;               // strip of trailig ':' from name
10717                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10718                 *r = c;
10719                 SendToProgram(buf,cps);     // send egtbpath command for this format
10720             }
10721             if(*p == ',') p++; // read away comma to position for next format name
10722         }
10723 }
10724
10725 static int
10726 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10727 {
10728       int width = 8, height = 8, holdings = 0;             // most common sizes
10729       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10730       // correct the deviations default for each variant
10731       if( v == VariantXiangqi ) width = 9,  height = 10;
10732       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10733       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10734       if( v == VariantCapablanca || v == VariantCapaRandom ||
10735           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10736                                 width = 10;
10737       if( v == VariantCourier ) width = 12;
10738       if( v == VariantSuper )                            holdings = 8;
10739       if( v == VariantGreat )   width = 10,              holdings = 8;
10740       if( v == VariantSChess )                           holdings = 7;
10741       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10742       if( v == VariantChuChess) width = 10, height = 10;
10743       if( v == VariantChu )     width = 12, height = 12;
10744       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10745              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10746              holdingsSize >= 0 && holdingsSize != holdings;
10747 }
10748
10749 char variantError[MSG_SIZ];
10750
10751 char *
10752 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10753 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10754       char *p, *variant = VariantName(v);
10755       static char b[MSG_SIZ];
10756       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10757            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10758                                                holdingsSize, variant); // cook up sized variant name
10759            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10760            if(StrStr(list, b) == NULL) {
10761                // specific sized variant not known, check if general sizing allowed
10762                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10763                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10764                             boardWidth, boardHeight, holdingsSize, engine);
10765                    return NULL;
10766                }
10767                /* [HGM] here we really should compare with the maximum supported board size */
10768            }
10769       } else snprintf(b, MSG_SIZ,"%s", variant);
10770       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10771       p = StrStr(list, b);
10772       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10773       if(p == NULL) {
10774           // occurs not at all in list, or only as sub-string
10775           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10776           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10777               int l = strlen(variantError);
10778               char *q;
10779               while(p != list && p[-1] != ',') p--;
10780               q = strchr(p, ',');
10781               if(q) *q = NULLCHAR;
10782               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10783               if(q) *q= ',';
10784           }
10785           return NULL;
10786       }
10787       return b;
10788 }
10789
10790 void
10791 InitChessProgram (ChessProgramState *cps, int setup)
10792 /* setup needed to setup FRC opening position */
10793 {
10794     char buf[MSG_SIZ], *b;
10795     if (appData.noChessProgram) return;
10796     hintRequested = FALSE;
10797     bookRequested = FALSE;
10798
10799     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10800     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10801     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10802     if(cps->memSize) { /* [HGM] memory */
10803       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10804         SendToProgram(buf, cps);
10805     }
10806     SendEgtPath(cps); /* [HGM] EGT */
10807     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10808       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10809         SendToProgram(buf, cps);
10810     }
10811
10812     setboardSpoiledMachineBlack = FALSE;
10813     SendToProgram(cps->initString, cps);
10814     if (gameInfo.variant != VariantNormal &&
10815         gameInfo.variant != VariantLoadable
10816         /* [HGM] also send variant if board size non-standard */
10817         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10818
10819       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10820                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10821
10822       if (b == NULL) {
10823         VariantClass v;
10824         char c, *q = cps->variants, *p = strchr(q, ',');
10825         if(p) *p = NULLCHAR;
10826         v = StringToVariant(q);
10827         DisplayError(variantError, 0);
10828         if(v != VariantUnknown && cps == &first) {
10829             int w, h, s;
10830             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10831                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10832             ASSIGN(appData.variant, q);
10833             Reset(TRUE, FALSE);
10834         }
10835         if(p) *p = ',';
10836         return;
10837       }
10838
10839       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10840       SendToProgram(buf, cps);
10841     }
10842     currentlyInitializedVariant = gameInfo.variant;
10843
10844     /* [HGM] send opening position in FRC to first engine */
10845     if(setup) {
10846           SendToProgram("force\n", cps);
10847           SendBoard(cps, 0);
10848           /* engine is now in force mode! Set flag to wake it up after first move. */
10849           setboardSpoiledMachineBlack = 1;
10850     }
10851
10852     if (cps->sendICS) {
10853       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10854       SendToProgram(buf, cps);
10855     }
10856     cps->maybeThinking = FALSE;
10857     cps->offeredDraw = 0;
10858     if (!appData.icsActive) {
10859         SendTimeControl(cps, movesPerSession, timeControl,
10860                         timeIncrement, appData.searchDepth,
10861                         searchTime);
10862     }
10863     if (appData.showThinking
10864         // [HGM] thinking: four options require thinking output to be sent
10865         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10866                                 ) {
10867         SendToProgram("post\n", cps);
10868     }
10869     SendToProgram("hard\n", cps);
10870     if (!appData.ponderNextMove) {
10871         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10872            it without being sure what state we are in first.  "hard"
10873            is not a toggle, so that one is OK.
10874          */
10875         SendToProgram("easy\n", cps);
10876     }
10877     if (cps->usePing) {
10878       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10879       SendToProgram(buf, cps);
10880     }
10881     cps->initDone = TRUE;
10882     ClearEngineOutputPane(cps == &second);
10883 }
10884
10885
10886 void
10887 ResendOptions (ChessProgramState *cps)
10888 { // send the stored value of the options
10889   int i;
10890   char buf[MSG_SIZ];
10891   Option *opt = cps->option;
10892   for(i=0; i<cps->nrOptions; i++, opt++) {
10893       switch(opt->type) {
10894         case Spin:
10895         case Slider:
10896         case CheckBox:
10897             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10898           break;
10899         case ComboBox:
10900           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10901           break;
10902         default:
10903             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10904           break;
10905         case Button:
10906         case SaveButton:
10907           continue;
10908       }
10909       SendToProgram(buf, cps);
10910   }
10911 }
10912
10913 void
10914 StartChessProgram (ChessProgramState *cps)
10915 {
10916     char buf[MSG_SIZ];
10917     int err;
10918
10919     if (appData.noChessProgram) return;
10920     cps->initDone = FALSE;
10921
10922     if (strcmp(cps->host, "localhost") == 0) {
10923         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10924     } else if (*appData.remoteShell == NULLCHAR) {
10925         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10926     } else {
10927         if (*appData.remoteUser == NULLCHAR) {
10928           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10929                     cps->program);
10930         } else {
10931           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10932                     cps->host, appData.remoteUser, cps->program);
10933         }
10934         err = StartChildProcess(buf, "", &cps->pr);
10935     }
10936
10937     if (err != 0) {
10938       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10939         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10940         if(cps != &first) return;
10941         appData.noChessProgram = TRUE;
10942         ThawUI();
10943         SetNCPMode();
10944 //      DisplayFatalError(buf, err, 1);
10945 //      cps->pr = NoProc;
10946 //      cps->isr = NULL;
10947         return;
10948     }
10949
10950     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10951     if (cps->protocolVersion > 1) {
10952       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10953       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10954         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10955         cps->comboCnt = 0;  //                and values of combo boxes
10956       }
10957       SendToProgram(buf, cps);
10958       if(cps->reload) ResendOptions(cps);
10959     } else {
10960       SendToProgram("xboard\n", cps);
10961     }
10962 }
10963
10964 void
10965 TwoMachinesEventIfReady P((void))
10966 {
10967   static int curMess = 0;
10968   if (first.lastPing != first.lastPong) {
10969     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10970     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10971     return;
10972   }
10973   if (second.lastPing != second.lastPong) {
10974     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10975     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10976     return;
10977   }
10978   DisplayMessage("", ""); curMess = 0;
10979   TwoMachinesEvent();
10980 }
10981
10982 char *
10983 MakeName (char *template)
10984 {
10985     time_t clock;
10986     struct tm *tm;
10987     static char buf[MSG_SIZ];
10988     char *p = buf;
10989     int i;
10990
10991     clock = time((time_t *)NULL);
10992     tm = localtime(&clock);
10993
10994     while(*p++ = *template++) if(p[-1] == '%') {
10995         switch(*template++) {
10996           case 0:   *p = 0; return buf;
10997           case 'Y': i = tm->tm_year+1900; break;
10998           case 'y': i = tm->tm_year-100; break;
10999           case 'M': i = tm->tm_mon+1; break;
11000           case 'd': i = tm->tm_mday; break;
11001           case 'h': i = tm->tm_hour; break;
11002           case 'm': i = tm->tm_min; break;
11003           case 's': i = tm->tm_sec; break;
11004           default:  i = 0;
11005         }
11006         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11007     }
11008     return buf;
11009 }
11010
11011 int
11012 CountPlayers (char *p)
11013 {
11014     int n = 0;
11015     while(p = strchr(p, '\n')) p++, n++; // count participants
11016     return n;
11017 }
11018
11019 FILE *
11020 WriteTourneyFile (char *results, FILE *f)
11021 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11022     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11023     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11024         // create a file with tournament description
11025         fprintf(f, "-participants {%s}\n", appData.participants);
11026         fprintf(f, "-seedBase %d\n", appData.seedBase);
11027         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11028         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11029         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11030         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11031         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11032         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11033         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11034         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11035         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11036         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11037         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11038         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11039         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11040         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11041         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11042         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11043         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11044         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11045         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11046         fprintf(f, "-smpCores %d\n", appData.smpCores);
11047         if(searchTime > 0)
11048                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11049         else {
11050                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11051                 fprintf(f, "-tc %s\n", appData.timeControl);
11052                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11053         }
11054         fprintf(f, "-results \"%s\"\n", results);
11055     }
11056     return f;
11057 }
11058
11059 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11060
11061 void
11062 Substitute (char *participants, int expunge)
11063 {
11064     int i, changed, changes=0, nPlayers=0;
11065     char *p, *q, *r, buf[MSG_SIZ];
11066     if(participants == NULL) return;
11067     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11068     r = p = participants; q = appData.participants;
11069     while(*p && *p == *q) {
11070         if(*p == '\n') r = p+1, nPlayers++;
11071         p++; q++;
11072     }
11073     if(*p) { // difference
11074         while(*p && *p++ != '\n');
11075         while(*q && *q++ != '\n');
11076       changed = nPlayers;
11077         changes = 1 + (strcmp(p, q) != 0);
11078     }
11079     if(changes == 1) { // a single engine mnemonic was changed
11080         q = r; while(*q) nPlayers += (*q++ == '\n');
11081         p = buf; while(*r && (*p = *r++) != '\n') p++;
11082         *p = NULLCHAR;
11083         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11084         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11085         if(mnemonic[i]) { // The substitute is valid
11086             FILE *f;
11087             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11088                 flock(fileno(f), LOCK_EX);
11089                 ParseArgsFromFile(f);
11090                 fseek(f, 0, SEEK_SET);
11091                 FREE(appData.participants); appData.participants = participants;
11092                 if(expunge) { // erase results of replaced engine
11093                     int len = strlen(appData.results), w, b, dummy;
11094                     for(i=0; i<len; i++) {
11095                         Pairing(i, nPlayers, &w, &b, &dummy);
11096                         if((w == changed || b == changed) && appData.results[i] == '*') {
11097                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11098                             fclose(f);
11099                             return;
11100                         }
11101                     }
11102                     for(i=0; i<len; i++) {
11103                         Pairing(i, nPlayers, &w, &b, &dummy);
11104                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11105                     }
11106                 }
11107                 WriteTourneyFile(appData.results, f);
11108                 fclose(f); // release lock
11109                 return;
11110             }
11111         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11112     }
11113     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11114     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11115     free(participants);
11116     return;
11117 }
11118
11119 int
11120 CheckPlayers (char *participants)
11121 {
11122         int i;
11123         char buf[MSG_SIZ], *p;
11124         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11125         while(p = strchr(participants, '\n')) {
11126             *p = NULLCHAR;
11127             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11128             if(!mnemonic[i]) {
11129                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11130                 *p = '\n';
11131                 DisplayError(buf, 0);
11132                 return 1;
11133             }
11134             *p = '\n';
11135             participants = p + 1;
11136         }
11137         return 0;
11138 }
11139
11140 int
11141 CreateTourney (char *name)
11142 {
11143         FILE *f;
11144         if(matchMode && strcmp(name, appData.tourneyFile)) {
11145              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11146         }
11147         if(name[0] == NULLCHAR) {
11148             if(appData.participants[0])
11149                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11150             return 0;
11151         }
11152         f = fopen(name, "r");
11153         if(f) { // file exists
11154             ASSIGN(appData.tourneyFile, name);
11155             ParseArgsFromFile(f); // parse it
11156         } else {
11157             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11158             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11159                 DisplayError(_("Not enough participants"), 0);
11160                 return 0;
11161             }
11162             if(CheckPlayers(appData.participants)) return 0;
11163             ASSIGN(appData.tourneyFile, name);
11164             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11165             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11166         }
11167         fclose(f);
11168         appData.noChessProgram = FALSE;
11169         appData.clockMode = TRUE;
11170         SetGNUMode();
11171         return 1;
11172 }
11173
11174 int
11175 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11176 {
11177     char buf[MSG_SIZ], *p, *q;
11178     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11179     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11180     skip = !all && group[0]; // if group requested, we start in skip mode
11181     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11182         p = names; q = buf; header = 0;
11183         while(*p && *p != '\n') *q++ = *p++;
11184         *q = 0;
11185         if(*p == '\n') p++;
11186         if(buf[0] == '#') {
11187             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11188             depth++; // we must be entering a new group
11189             if(all) continue; // suppress printing group headers when complete list requested
11190             header = 1;
11191             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11192         }
11193         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11194         if(engineList[i]) free(engineList[i]);
11195         engineList[i] = strdup(buf);
11196         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11197         if(engineMnemonic[i]) free(engineMnemonic[i]);
11198         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11199             strcat(buf, " (");
11200             sscanf(q + 8, "%s", buf + strlen(buf));
11201             strcat(buf, ")");
11202         }
11203         engineMnemonic[i] = strdup(buf);
11204         i++;
11205     }
11206     engineList[i] = engineMnemonic[i] = NULL;
11207     return i;
11208 }
11209
11210 // following implemented as macro to avoid type limitations
11211 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11212
11213 void
11214 SwapEngines (int n)
11215 {   // swap settings for first engine and other engine (so far only some selected options)
11216     int h;
11217     char *p;
11218     if(n == 0) return;
11219     SWAP(directory, p)
11220     SWAP(chessProgram, p)
11221     SWAP(isUCI, h)
11222     SWAP(hasOwnBookUCI, h)
11223     SWAP(protocolVersion, h)
11224     SWAP(reuse, h)
11225     SWAP(scoreIsAbsolute, h)
11226     SWAP(timeOdds, h)
11227     SWAP(logo, p)
11228     SWAP(pgnName, p)
11229     SWAP(pvSAN, h)
11230     SWAP(engOptions, p)
11231     SWAP(engInitString, p)
11232     SWAP(computerString, p)
11233     SWAP(features, p)
11234     SWAP(fenOverride, p)
11235     SWAP(NPS, h)
11236     SWAP(accumulateTC, h)
11237     SWAP(drawDepth, h)
11238     SWAP(host, p)
11239     SWAP(pseudo, h)
11240 }
11241
11242 int
11243 GetEngineLine (char *s, int n)
11244 {
11245     int i;
11246     char buf[MSG_SIZ];
11247     extern char *icsNames;
11248     if(!s || !*s) return 0;
11249     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11250     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11251     if(!mnemonic[i]) return 0;
11252     if(n == 11) return 1; // just testing if there was a match
11253     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11254     if(n == 1) SwapEngines(n);
11255     ParseArgsFromString(buf);
11256     if(n == 1) SwapEngines(n);
11257     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11258         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11259         ParseArgsFromString(buf);
11260     }
11261     return 1;
11262 }
11263
11264 int
11265 SetPlayer (int player, char *p)
11266 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11267     int i;
11268     char buf[MSG_SIZ], *engineName;
11269     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11270     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11271     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11272     if(mnemonic[i]) {
11273         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11274         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11275         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11276         ParseArgsFromString(buf);
11277     } else { // no engine with this nickname is installed!
11278         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11279         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11280         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11281         ModeHighlight();
11282         DisplayError(buf, 0);
11283         return 0;
11284     }
11285     free(engineName);
11286     return i;
11287 }
11288
11289 char *recentEngines;
11290
11291 void
11292 RecentEngineEvent (int nr)
11293 {
11294     int n;
11295 //    SwapEngines(1); // bump first to second
11296 //    ReplaceEngine(&second, 1); // and load it there
11297     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11298     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11299     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11300         ReplaceEngine(&first, 0);
11301         FloatToFront(&appData.recentEngineList, command[n]);
11302     }
11303 }
11304
11305 int
11306 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11307 {   // determine players from game number
11308     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11309
11310     if(appData.tourneyType == 0) {
11311         roundsPerCycle = (nPlayers - 1) | 1;
11312         pairingsPerRound = nPlayers / 2;
11313     } else if(appData.tourneyType > 0) {
11314         roundsPerCycle = nPlayers - appData.tourneyType;
11315         pairingsPerRound = appData.tourneyType;
11316     }
11317     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11318     gamesPerCycle = gamesPerRound * roundsPerCycle;
11319     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11320     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11321     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11322     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11323     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11324     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11325
11326     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11327     if(appData.roundSync) *syncInterval = gamesPerRound;
11328
11329     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11330
11331     if(appData.tourneyType == 0) {
11332         if(curPairing == (nPlayers-1)/2 ) {
11333             *whitePlayer = curRound;
11334             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11335         } else {
11336             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11337             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11338             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11339             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11340         }
11341     } else if(appData.tourneyType > 1) {
11342         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11343         *whitePlayer = curRound + appData.tourneyType;
11344     } else if(appData.tourneyType > 0) {
11345         *whitePlayer = curPairing;
11346         *blackPlayer = curRound + appData.tourneyType;
11347     }
11348
11349     // take care of white/black alternation per round.
11350     // For cycles and games this is already taken care of by default, derived from matchGame!
11351     return curRound & 1;
11352 }
11353
11354 int
11355 NextTourneyGame (int nr, int *swapColors)
11356 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11357     char *p, *q;
11358     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11359     FILE *tf;
11360     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11361     tf = fopen(appData.tourneyFile, "r");
11362     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11363     ParseArgsFromFile(tf); fclose(tf);
11364     InitTimeControls(); // TC might be altered from tourney file
11365
11366     nPlayers = CountPlayers(appData.participants); // count participants
11367     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11368     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11369
11370     if(syncInterval) {
11371         p = q = appData.results;
11372         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11373         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11374             DisplayMessage(_("Waiting for other game(s)"),"");
11375             waitingForGame = TRUE;
11376             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11377             return 0;
11378         }
11379         waitingForGame = FALSE;
11380     }
11381
11382     if(appData.tourneyType < 0) {
11383         if(nr>=0 && !pairingReceived) {
11384             char buf[1<<16];
11385             if(pairing.pr == NoProc) {
11386                 if(!appData.pairingEngine[0]) {
11387                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11388                     return 0;
11389                 }
11390                 StartChessProgram(&pairing); // starts the pairing engine
11391             }
11392             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11393             SendToProgram(buf, &pairing);
11394             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11395             SendToProgram(buf, &pairing);
11396             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11397         }
11398         pairingReceived = 0;                              // ... so we continue here
11399         *swapColors = 0;
11400         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11401         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11402         matchGame = 1; roundNr = nr / syncInterval + 1;
11403     }
11404
11405     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11406
11407     // redefine engines, engine dir, etc.
11408     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11409     if(first.pr == NoProc) {
11410       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11411       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11412     }
11413     if(second.pr == NoProc) {
11414       SwapEngines(1);
11415       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11416       SwapEngines(1);         // and make that valid for second engine by swapping
11417       InitEngine(&second, 1);
11418     }
11419     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11420     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11421     return OK;
11422 }
11423
11424 void
11425 NextMatchGame ()
11426 {   // performs game initialization that does not invoke engines, and then tries to start the game
11427     int res, firstWhite, swapColors = 0;
11428     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11429     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
11430         char buf[MSG_SIZ];
11431         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11432         if(strcmp(buf, currentDebugFile)) { // name has changed
11433             FILE *f = fopen(buf, "w");
11434             if(f) { // if opening the new file failed, just keep using the old one
11435                 ASSIGN(currentDebugFile, buf);
11436                 fclose(debugFP);
11437                 debugFP = f;
11438             }
11439             if(appData.serverFileName) {
11440                 if(serverFP) fclose(serverFP);
11441                 serverFP = fopen(appData.serverFileName, "w");
11442                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11443                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11444             }
11445         }
11446     }
11447     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11448     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11449     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11450     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11451     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11452     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11453     Reset(FALSE, first.pr != NoProc);
11454     res = LoadGameOrPosition(matchGame); // setup game
11455     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11456     if(!res) return; // abort when bad game/pos file
11457     if(appData.epd) {// in EPD mode we make sure first engine is to move
11458         firstWhite = !(forwardMostMove & 1);
11459         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11460         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11461     }
11462     TwoMachinesEvent();
11463 }
11464
11465 void
11466 UserAdjudicationEvent (int result)
11467 {
11468     ChessMove gameResult = GameIsDrawn;
11469
11470     if( result > 0 ) {
11471         gameResult = WhiteWins;
11472     }
11473     else if( result < 0 ) {
11474         gameResult = BlackWins;
11475     }
11476
11477     if( gameMode == TwoMachinesPlay ) {
11478         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11479     }
11480 }
11481
11482
11483 // [HGM] save: calculate checksum of game to make games easily identifiable
11484 int
11485 StringCheckSum (char *s)
11486 {
11487         int i = 0;
11488         if(s==NULL) return 0;
11489         while(*s) i = i*259 + *s++;
11490         return i;
11491 }
11492
11493 int
11494 GameCheckSum ()
11495 {
11496         int i, sum=0;
11497         for(i=backwardMostMove; i<forwardMostMove; i++) {
11498                 sum += pvInfoList[i].depth;
11499                 sum += StringCheckSum(parseList[i]);
11500                 sum += StringCheckSum(commentList[i]);
11501                 sum *= 261;
11502         }
11503         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11504         return sum + StringCheckSum(commentList[i]);
11505 } // end of save patch
11506
11507 void
11508 GameEnds (ChessMove result, char *resultDetails, int whosays)
11509 {
11510     GameMode nextGameMode;
11511     int isIcsGame;
11512     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11513
11514     if(endingGame) return; /* [HGM] crash: forbid recursion */
11515     endingGame = 1;
11516     if(twoBoards) { // [HGM] dual: switch back to one board
11517         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11518         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11519     }
11520     if (appData.debugMode) {
11521       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11522               result, resultDetails ? resultDetails : "(null)", whosays);
11523     }
11524
11525     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11526
11527     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11528
11529     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11530         /* If we are playing on ICS, the server decides when the
11531            game is over, but the engine can offer to draw, claim
11532            a draw, or resign.
11533          */
11534 #if ZIPPY
11535         if (appData.zippyPlay && first.initDone) {
11536             if (result == GameIsDrawn) {
11537                 /* In case draw still needs to be claimed */
11538                 SendToICS(ics_prefix);
11539                 SendToICS("draw\n");
11540             } else if (StrCaseStr(resultDetails, "resign")) {
11541                 SendToICS(ics_prefix);
11542                 SendToICS("resign\n");
11543             }
11544         }
11545 #endif
11546         endingGame = 0; /* [HGM] crash */
11547         return;
11548     }
11549
11550     /* If we're loading the game from a file, stop */
11551     if (whosays == GE_FILE) {
11552       (void) StopLoadGameTimer();
11553       gameFileFP = NULL;
11554     }
11555
11556     /* Cancel draw offers */
11557     first.offeredDraw = second.offeredDraw = 0;
11558
11559     /* If this is an ICS game, only ICS can really say it's done;
11560        if not, anyone can. */
11561     isIcsGame = (gameMode == IcsPlayingWhite ||
11562                  gameMode == IcsPlayingBlack ||
11563                  gameMode == IcsObserving    ||
11564                  gameMode == IcsExamining);
11565
11566     if (!isIcsGame || whosays == GE_ICS) {
11567         /* OK -- not an ICS game, or ICS said it was done */
11568         StopClocks();
11569         if (!isIcsGame && !appData.noChessProgram)
11570           SetUserThinkingEnables();
11571
11572         /* [HGM] if a machine claims the game end we verify this claim */
11573         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11574             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11575                 char claimer;
11576                 ChessMove trueResult = (ChessMove) -1;
11577
11578                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11579                                             first.twoMachinesColor[0] :
11580                                             second.twoMachinesColor[0] ;
11581
11582                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11583                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11584                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11585                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11586                 } else
11587                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11588                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11589                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11590                 } else
11591                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11592                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11593                 }
11594
11595                 // now verify win claims, but not in drop games, as we don't understand those yet
11596                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11597                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11598                     (result == WhiteWins && claimer == 'w' ||
11599                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11600                       if (appData.debugMode) {
11601                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11602                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11603                       }
11604                       if(result != trueResult) {
11605                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11606                               result = claimer == 'w' ? BlackWins : WhiteWins;
11607                               resultDetails = buf;
11608                       }
11609                 } else
11610                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11611                     && (forwardMostMove <= backwardMostMove ||
11612                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11613                         (claimer=='b')==(forwardMostMove&1))
11614                                                                                   ) {
11615                       /* [HGM] verify: draws that were not flagged are false claims */
11616                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11617                       result = claimer == 'w' ? BlackWins : WhiteWins;
11618                       resultDetails = buf;
11619                 }
11620                 /* (Claiming a loss is accepted no questions asked!) */
11621             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11622                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11623                 result = GameUnfinished;
11624                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11625             }
11626             /* [HGM] bare: don't allow bare King to win */
11627             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11628                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11629                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11630                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11631                && result != GameIsDrawn)
11632             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11633                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11634                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11635                         if(p >= 0 && p <= (int)WhiteKing) k++;
11636                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11637                 }
11638                 if (appData.debugMode) {
11639                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11640                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11641                 }
11642                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11643                         result = GameIsDrawn;
11644                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11645                         resultDetails = buf;
11646                 }
11647             }
11648         }
11649
11650
11651         if(serverMoves != NULL && !loadFlag) { char c = '=';
11652             if(result==WhiteWins) c = '+';
11653             if(result==BlackWins) c = '-';
11654             if(resultDetails != NULL)
11655                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11656         }
11657         if (resultDetails != NULL) {
11658             gameInfo.result = result;
11659             gameInfo.resultDetails = StrSave(resultDetails);
11660
11661             /* display last move only if game was not loaded from file */
11662             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11663                 DisplayMove(currentMove - 1);
11664
11665             if (forwardMostMove != 0) {
11666                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11667                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11668                                                                 ) {
11669                     if (*appData.saveGameFile != NULLCHAR) {
11670                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11671                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11672                         else
11673                         SaveGameToFile(appData.saveGameFile, TRUE);
11674                     } else if (appData.autoSaveGames) {
11675                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11676                     }
11677                     if (*appData.savePositionFile != NULLCHAR) {
11678                         SavePositionToFile(appData.savePositionFile);
11679                     }
11680                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11681                 }
11682             }
11683
11684             /* Tell program how game ended in case it is learning */
11685             /* [HGM] Moved this to after saving the PGN, just in case */
11686             /* engine died and we got here through time loss. In that */
11687             /* case we will get a fatal error writing the pipe, which */
11688             /* would otherwise lose us the PGN.                       */
11689             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11690             /* output during GameEnds should never be fatal anymore   */
11691             if (gameMode == MachinePlaysWhite ||
11692                 gameMode == MachinePlaysBlack ||
11693                 gameMode == TwoMachinesPlay ||
11694                 gameMode == IcsPlayingWhite ||
11695                 gameMode == IcsPlayingBlack ||
11696                 gameMode == BeginningOfGame) {
11697                 char buf[MSG_SIZ];
11698                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11699                         resultDetails);
11700                 if (first.pr != NoProc) {
11701                     SendToProgram(buf, &first);
11702                 }
11703                 if (second.pr != NoProc &&
11704                     gameMode == TwoMachinesPlay) {
11705                     SendToProgram(buf, &second);
11706                 }
11707             }
11708         }
11709
11710         if (appData.icsActive) {
11711             if (appData.quietPlay &&
11712                 (gameMode == IcsPlayingWhite ||
11713                  gameMode == IcsPlayingBlack)) {
11714                 SendToICS(ics_prefix);
11715                 SendToICS("set shout 1\n");
11716             }
11717             nextGameMode = IcsIdle;
11718             ics_user_moved = FALSE;
11719             /* clean up premove.  It's ugly when the game has ended and the
11720              * premove highlights are still on the board.
11721              */
11722             if (gotPremove) {
11723               gotPremove = FALSE;
11724               ClearPremoveHighlights();
11725               DrawPosition(FALSE, boards[currentMove]);
11726             }
11727             if (whosays == GE_ICS) {
11728                 switch (result) {
11729                 case WhiteWins:
11730                     if (gameMode == IcsPlayingWhite)
11731                         PlayIcsWinSound();
11732                     else if(gameMode == IcsPlayingBlack)
11733                         PlayIcsLossSound();
11734                     break;
11735                 case BlackWins:
11736                     if (gameMode == IcsPlayingBlack)
11737                         PlayIcsWinSound();
11738                     else if(gameMode == IcsPlayingWhite)
11739                         PlayIcsLossSound();
11740                     break;
11741                 case GameIsDrawn:
11742                     PlayIcsDrawSound();
11743                     break;
11744                 default:
11745                     PlayIcsUnfinishedSound();
11746                 }
11747             }
11748             if(appData.quitNext) { ExitEvent(0); return; }
11749         } else if (gameMode == EditGame ||
11750                    gameMode == PlayFromGameFile ||
11751                    gameMode == AnalyzeMode ||
11752                    gameMode == AnalyzeFile) {
11753             nextGameMode = gameMode;
11754         } else {
11755             nextGameMode = EndOfGame;
11756         }
11757         pausing = FALSE;
11758         ModeHighlight();
11759     } else {
11760         nextGameMode = gameMode;
11761     }
11762
11763     if (appData.noChessProgram) {
11764         gameMode = nextGameMode;
11765         ModeHighlight();
11766         endingGame = 0; /* [HGM] crash */
11767         return;
11768     }
11769
11770     if (first.reuse) {
11771         /* Put first chess program into idle state */
11772         if (first.pr != NoProc &&
11773             (gameMode == MachinePlaysWhite ||
11774              gameMode == MachinePlaysBlack ||
11775              gameMode == TwoMachinesPlay ||
11776              gameMode == IcsPlayingWhite ||
11777              gameMode == IcsPlayingBlack ||
11778              gameMode == BeginningOfGame)) {
11779             SendToProgram("force\n", &first);
11780             if (first.usePing) {
11781               char buf[MSG_SIZ];
11782               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11783               SendToProgram(buf, &first);
11784             }
11785         }
11786     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11787         /* Kill off first chess program */
11788         if (first.isr != NULL)
11789           RemoveInputSource(first.isr);
11790         first.isr = NULL;
11791
11792         if (first.pr != NoProc) {
11793             ExitAnalyzeMode();
11794             DoSleep( appData.delayBeforeQuit );
11795             SendToProgram("quit\n", &first);
11796             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11797             first.reload = TRUE;
11798         }
11799         first.pr = NoProc;
11800     }
11801     if (second.reuse) {
11802         /* Put second chess program into idle state */
11803         if (second.pr != NoProc &&
11804             gameMode == TwoMachinesPlay) {
11805             SendToProgram("force\n", &second);
11806             if (second.usePing) {
11807               char buf[MSG_SIZ];
11808               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11809               SendToProgram(buf, &second);
11810             }
11811         }
11812     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11813         /* Kill off second chess program */
11814         if (second.isr != NULL)
11815           RemoveInputSource(second.isr);
11816         second.isr = NULL;
11817
11818         if (second.pr != NoProc) {
11819             DoSleep( appData.delayBeforeQuit );
11820             SendToProgram("quit\n", &second);
11821             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11822             second.reload = TRUE;
11823         }
11824         second.pr = NoProc;
11825     }
11826
11827     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11828         char resChar = '=';
11829         switch (result) {
11830         case WhiteWins:
11831           resChar = '+';
11832           if (first.twoMachinesColor[0] == 'w') {
11833             first.matchWins++;
11834           } else {
11835             second.matchWins++;
11836           }
11837           break;
11838         case BlackWins:
11839           resChar = '-';
11840           if (first.twoMachinesColor[0] == 'b') {
11841             first.matchWins++;
11842           } else {
11843             second.matchWins++;
11844           }
11845           break;
11846         case GameUnfinished:
11847           resChar = ' ';
11848         default:
11849           break;
11850         }
11851
11852         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11853         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11854             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11855             ReserveGame(nextGame, resChar); // sets nextGame
11856             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11857             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11858         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11859
11860         if (nextGame <= appData.matchGames && !abortMatch) {
11861             gameMode = nextGameMode;
11862             matchGame = nextGame; // this will be overruled in tourney mode!
11863             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11864             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11865             endingGame = 0; /* [HGM] crash */
11866             return;
11867         } else {
11868             gameMode = nextGameMode;
11869             if(appData.epd) {
11870                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11871                 OutputKibitz(2, buf);
11872                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11873                 OutputKibitz(2, buf);
11874                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11875                 if(second.matchWins) OutputKibitz(2, buf);
11876                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11877                 OutputKibitz(2, buf);
11878             }
11879             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11880                      first.tidy, second.tidy,
11881                      first.matchWins, second.matchWins,
11882                      appData.matchGames - (first.matchWins + second.matchWins));
11883             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11884             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11885             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11886             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11887                 first.twoMachinesColor = "black\n";
11888                 second.twoMachinesColor = "white\n";
11889             } else {
11890                 first.twoMachinesColor = "white\n";
11891                 second.twoMachinesColor = "black\n";
11892             }
11893         }
11894     }
11895     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11896         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11897       ExitAnalyzeMode();
11898     gameMode = nextGameMode;
11899     ModeHighlight();
11900     endingGame = 0;  /* [HGM] crash */
11901     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11902         if(matchMode == TRUE) { // match through command line: exit with or without popup
11903             if(ranking) {
11904                 ToNrEvent(forwardMostMove);
11905                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11906                 else ExitEvent(0);
11907             } else DisplayFatalError(buf, 0, 0);
11908         } else { // match through menu; just stop, with or without popup
11909             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11910             ModeHighlight();
11911             if(ranking){
11912                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11913             } else DisplayNote(buf);
11914       }
11915       if(ranking) free(ranking);
11916     }
11917 }
11918
11919 /* Assumes program was just initialized (initString sent).
11920    Leaves program in force mode. */
11921 void
11922 FeedMovesToProgram (ChessProgramState *cps, int upto)
11923 {
11924     int i;
11925
11926     if (appData.debugMode)
11927       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11928               startedFromSetupPosition ? "position and " : "",
11929               backwardMostMove, upto, cps->which);
11930     if(currentlyInitializedVariant != gameInfo.variant) {
11931       char buf[MSG_SIZ];
11932         // [HGM] variantswitch: make engine aware of new variant
11933         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11934                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11935                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11936         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11937         SendToProgram(buf, cps);
11938         currentlyInitializedVariant = gameInfo.variant;
11939     }
11940     SendToProgram("force\n", cps);
11941     if (startedFromSetupPosition) {
11942         SendBoard(cps, backwardMostMove);
11943     if (appData.debugMode) {
11944         fprintf(debugFP, "feedMoves\n");
11945     }
11946     }
11947     for (i = backwardMostMove; i < upto; i++) {
11948         SendMoveToProgram(i, cps);
11949     }
11950 }
11951
11952
11953 int
11954 ResurrectChessProgram ()
11955 {
11956      /* The chess program may have exited.
11957         If so, restart it and feed it all the moves made so far. */
11958     static int doInit = 0;
11959
11960     if (appData.noChessProgram) return 1;
11961
11962     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11963         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11964         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11965         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11966     } else {
11967         if (first.pr != NoProc) return 1;
11968         StartChessProgram(&first);
11969     }
11970     InitChessProgram(&first, FALSE);
11971     FeedMovesToProgram(&first, currentMove);
11972
11973     if (!first.sendTime) {
11974         /* can't tell gnuchess what its clock should read,
11975            so we bow to its notion. */
11976         ResetClocks();
11977         timeRemaining[0][currentMove] = whiteTimeRemaining;
11978         timeRemaining[1][currentMove] = blackTimeRemaining;
11979     }
11980
11981     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11982                 appData.icsEngineAnalyze) && first.analysisSupport) {
11983       SendToProgram("analyze\n", &first);
11984       first.analyzing = TRUE;
11985     }
11986     return 1;
11987 }
11988
11989 /*
11990  * Button procedures
11991  */
11992 void
11993 Reset (int redraw, int init)
11994 {
11995     int i;
11996
11997     if (appData.debugMode) {
11998         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11999                 redraw, init, gameMode);
12000     }
12001     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12002     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12003     CleanupTail(); // [HGM] vari: delete any stored variations
12004     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12005     pausing = pauseExamInvalid = FALSE;
12006     startedFromSetupPosition = blackPlaysFirst = FALSE;
12007     firstMove = TRUE;
12008     whiteFlag = blackFlag = FALSE;
12009     userOfferedDraw = FALSE;
12010     hintRequested = bookRequested = FALSE;
12011     first.maybeThinking = FALSE;
12012     second.maybeThinking = FALSE;
12013     first.bookSuspend = FALSE; // [HGM] book
12014     second.bookSuspend = FALSE;
12015     thinkOutput[0] = NULLCHAR;
12016     lastHint[0] = NULLCHAR;
12017     ClearGameInfo(&gameInfo);
12018     gameInfo.variant = StringToVariant(appData.variant);
12019     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12020         gameInfo.variant = VariantUnknown;
12021         strncpy(engineVariant, appData.variant, MSG_SIZ);
12022     }
12023     ics_user_moved = ics_clock_paused = FALSE;
12024     ics_getting_history = H_FALSE;
12025     ics_gamenum = -1;
12026     white_holding[0] = black_holding[0] = NULLCHAR;
12027     ClearProgramStats();
12028     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12029
12030     ResetFrontEnd();
12031     ClearHighlights();
12032     flipView = appData.flipView;
12033     ClearPremoveHighlights();
12034     gotPremove = FALSE;
12035     alarmSounded = FALSE;
12036     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12037
12038     GameEnds(EndOfFile, NULL, GE_PLAYER);
12039     if(appData.serverMovesName != NULL) {
12040         /* [HGM] prepare to make moves file for broadcasting */
12041         clock_t t = clock();
12042         if(serverMoves != NULL) fclose(serverMoves);
12043         serverMoves = fopen(appData.serverMovesName, "r");
12044         if(serverMoves != NULL) {
12045             fclose(serverMoves);
12046             /* delay 15 sec before overwriting, so all clients can see end */
12047             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12048         }
12049         serverMoves = fopen(appData.serverMovesName, "w");
12050     }
12051
12052     ExitAnalyzeMode();
12053     gameMode = BeginningOfGame;
12054     ModeHighlight();
12055     if(appData.icsActive) gameInfo.variant = VariantNormal;
12056     currentMove = forwardMostMove = backwardMostMove = 0;
12057     MarkTargetSquares(1);
12058     InitPosition(redraw);
12059     for (i = 0; i < MAX_MOVES; i++) {
12060         if (commentList[i] != NULL) {
12061             free(commentList[i]);
12062             commentList[i] = NULL;
12063         }
12064     }
12065     ResetClocks();
12066     timeRemaining[0][0] = whiteTimeRemaining;
12067     timeRemaining[1][0] = blackTimeRemaining;
12068
12069     if (first.pr == NoProc) {
12070         StartChessProgram(&first);
12071     }
12072     if (init) {
12073             InitChessProgram(&first, startedFromSetupPosition);
12074     }
12075     DisplayTitle("");
12076     DisplayMessage("", "");
12077     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12078     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12079     ClearMap();        // [HGM] exclude: invalidate map
12080 }
12081
12082 void
12083 AutoPlayGameLoop ()
12084 {
12085     for (;;) {
12086         if (!AutoPlayOneMove())
12087           return;
12088         if (matchMode || appData.timeDelay == 0)
12089           continue;
12090         if (appData.timeDelay < 0)
12091           return;
12092         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12093         break;
12094     }
12095 }
12096
12097 void
12098 AnalyzeNextGame()
12099 {
12100     ReloadGame(1); // next game
12101 }
12102
12103 int
12104 AutoPlayOneMove ()
12105 {
12106     int fromX, fromY, toX, toY;
12107
12108     if (appData.debugMode) {
12109       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12110     }
12111
12112     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12113       return FALSE;
12114
12115     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12116       pvInfoList[currentMove].depth = programStats.depth;
12117       pvInfoList[currentMove].score = programStats.score;
12118       pvInfoList[currentMove].time  = 0;
12119       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12120       else { // append analysis of final position as comment
12121         char buf[MSG_SIZ];
12122         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12123         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12124       }
12125       programStats.depth = 0;
12126     }
12127
12128     if (currentMove >= forwardMostMove) {
12129       if(gameMode == AnalyzeFile) {
12130           if(appData.loadGameIndex == -1) {
12131             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12132           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12133           } else {
12134           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12135         }
12136       }
12137 //      gameMode = EndOfGame;
12138 //      ModeHighlight();
12139
12140       /* [AS] Clear current move marker at the end of a game */
12141       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12142
12143       return FALSE;
12144     }
12145
12146     toX = moveList[currentMove][2] - AAA;
12147     toY = moveList[currentMove][3] - ONE;
12148
12149     if (moveList[currentMove][1] == '@') {
12150         if (appData.highlightLastMove) {
12151             SetHighlights(-1, -1, toX, toY);
12152         }
12153     } else {
12154         fromX = moveList[currentMove][0] - AAA;
12155         fromY = moveList[currentMove][1] - ONE;
12156
12157         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12158
12159         if(moveList[currentMove][4] == ';') { // multi-leg
12160             killX = moveList[currentMove][5] - AAA;
12161             killY = moveList[currentMove][6] - ONE;
12162         }
12163         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12164         killX = killY = -1;
12165
12166         if (appData.highlightLastMove) {
12167             SetHighlights(fromX, fromY, toX, toY);
12168         }
12169     }
12170     DisplayMove(currentMove);
12171     SendMoveToProgram(currentMove++, &first);
12172     DisplayBothClocks();
12173     DrawPosition(FALSE, boards[currentMove]);
12174     // [HGM] PV info: always display, routine tests if empty
12175     DisplayComment(currentMove - 1, commentList[currentMove]);
12176     return TRUE;
12177 }
12178
12179
12180 int
12181 LoadGameOneMove (ChessMove readAhead)
12182 {
12183     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12184     char promoChar = NULLCHAR;
12185     ChessMove moveType;
12186     char move[MSG_SIZ];
12187     char *p, *q;
12188
12189     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12190         gameMode != AnalyzeMode && gameMode != Training) {
12191         gameFileFP = NULL;
12192         return FALSE;
12193     }
12194
12195     yyboardindex = forwardMostMove;
12196     if (readAhead != EndOfFile) {
12197       moveType = readAhead;
12198     } else {
12199       if (gameFileFP == NULL)
12200           return FALSE;
12201       moveType = (ChessMove) Myylex();
12202     }
12203
12204     done = FALSE;
12205     switch (moveType) {
12206       case Comment:
12207         if (appData.debugMode)
12208           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12209         p = yy_text;
12210
12211         /* append the comment but don't display it */
12212         AppendComment(currentMove, p, FALSE);
12213         return TRUE;
12214
12215       case WhiteCapturesEnPassant:
12216       case BlackCapturesEnPassant:
12217       case WhitePromotion:
12218       case BlackPromotion:
12219       case WhiteNonPromotion:
12220       case BlackNonPromotion:
12221       case NormalMove:
12222       case FirstLeg:
12223       case WhiteKingSideCastle:
12224       case WhiteQueenSideCastle:
12225       case BlackKingSideCastle:
12226       case BlackQueenSideCastle:
12227       case WhiteKingSideCastleWild:
12228       case WhiteQueenSideCastleWild:
12229       case BlackKingSideCastleWild:
12230       case BlackQueenSideCastleWild:
12231       /* PUSH Fabien */
12232       case WhiteHSideCastleFR:
12233       case WhiteASideCastleFR:
12234       case BlackHSideCastleFR:
12235       case BlackASideCastleFR:
12236       /* POP Fabien */
12237         if (appData.debugMode)
12238           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12239         fromX = currentMoveString[0] - AAA;
12240         fromY = currentMoveString[1] - ONE;
12241         toX = currentMoveString[2] - AAA;
12242         toY = currentMoveString[3] - ONE;
12243         promoChar = currentMoveString[4];
12244         if(promoChar == ';') promoChar = currentMoveString[7];
12245         break;
12246
12247       case WhiteDrop:
12248       case BlackDrop:
12249         if (appData.debugMode)
12250           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12251         fromX = moveType == WhiteDrop ?
12252           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12253         (int) CharToPiece(ToLower(currentMoveString[0]));
12254         fromY = DROP_RANK;
12255         toX = currentMoveString[2] - AAA;
12256         toY = currentMoveString[3] - ONE;
12257         break;
12258
12259       case WhiteWins:
12260       case BlackWins:
12261       case GameIsDrawn:
12262       case GameUnfinished:
12263         if (appData.debugMode)
12264           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12265         p = strchr(yy_text, '{');
12266         if (p == NULL) p = strchr(yy_text, '(');
12267         if (p == NULL) {
12268             p = yy_text;
12269             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12270         } else {
12271             q = strchr(p, *p == '{' ? '}' : ')');
12272             if (q != NULL) *q = NULLCHAR;
12273             p++;
12274         }
12275         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12276         GameEnds(moveType, p, GE_FILE);
12277         done = TRUE;
12278         if (cmailMsgLoaded) {
12279             ClearHighlights();
12280             flipView = WhiteOnMove(currentMove);
12281             if (moveType == GameUnfinished) flipView = !flipView;
12282             if (appData.debugMode)
12283               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12284         }
12285         break;
12286
12287       case EndOfFile:
12288         if (appData.debugMode)
12289           fprintf(debugFP, "Parser hit end of file\n");
12290         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12291           case MT_NONE:
12292           case MT_CHECK:
12293             break;
12294           case MT_CHECKMATE:
12295           case MT_STAINMATE:
12296             if (WhiteOnMove(currentMove)) {
12297                 GameEnds(BlackWins, "Black mates", GE_FILE);
12298             } else {
12299                 GameEnds(WhiteWins, "White mates", GE_FILE);
12300             }
12301             break;
12302           case MT_STALEMATE:
12303             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12304             break;
12305         }
12306         done = TRUE;
12307         break;
12308
12309       case MoveNumberOne:
12310         if (lastLoadGameStart == GNUChessGame) {
12311             /* GNUChessGames have numbers, but they aren't move numbers */
12312             if (appData.debugMode)
12313               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12314                       yy_text, (int) moveType);
12315             return LoadGameOneMove(EndOfFile); /* tail recursion */
12316         }
12317         /* else fall thru */
12318
12319       case XBoardGame:
12320       case GNUChessGame:
12321       case PGNTag:
12322         /* Reached start of next game in file */
12323         if (appData.debugMode)
12324           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12325         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12326           case MT_NONE:
12327           case MT_CHECK:
12328             break;
12329           case MT_CHECKMATE:
12330           case MT_STAINMATE:
12331             if (WhiteOnMove(currentMove)) {
12332                 GameEnds(BlackWins, "Black mates", GE_FILE);
12333             } else {
12334                 GameEnds(WhiteWins, "White mates", GE_FILE);
12335             }
12336             break;
12337           case MT_STALEMATE:
12338             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12339             break;
12340         }
12341         done = TRUE;
12342         break;
12343
12344       case PositionDiagram:     /* should not happen; ignore */
12345       case ElapsedTime:         /* ignore */
12346       case NAG:                 /* ignore */
12347         if (appData.debugMode)
12348           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12349                   yy_text, (int) moveType);
12350         return LoadGameOneMove(EndOfFile); /* tail recursion */
12351
12352       case IllegalMove:
12353         if (appData.testLegality) {
12354             if (appData.debugMode)
12355               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12356             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12357                     (forwardMostMove / 2) + 1,
12358                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12359             DisplayError(move, 0);
12360             done = TRUE;
12361         } else {
12362             if (appData.debugMode)
12363               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12364                       yy_text, currentMoveString);
12365             if(currentMoveString[1] == '@') {
12366                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12367                 fromY = DROP_RANK;
12368             } else {
12369                 fromX = currentMoveString[0] - AAA;
12370                 fromY = currentMoveString[1] - ONE;
12371             }
12372             toX = currentMoveString[2] - AAA;
12373             toY = currentMoveString[3] - ONE;
12374             promoChar = currentMoveString[4];
12375         }
12376         break;
12377
12378       case AmbiguousMove:
12379         if (appData.debugMode)
12380           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12381         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12382                 (forwardMostMove / 2) + 1,
12383                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12384         DisplayError(move, 0);
12385         done = TRUE;
12386         break;
12387
12388       default:
12389       case ImpossibleMove:
12390         if (appData.debugMode)
12391           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12392         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12393                 (forwardMostMove / 2) + 1,
12394                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12395         DisplayError(move, 0);
12396         done = TRUE;
12397         break;
12398     }
12399
12400     if (done) {
12401         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12402             DrawPosition(FALSE, boards[currentMove]);
12403             DisplayBothClocks();
12404             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12405               DisplayComment(currentMove - 1, commentList[currentMove]);
12406         }
12407         (void) StopLoadGameTimer();
12408         gameFileFP = NULL;
12409         cmailOldMove = forwardMostMove;
12410         return FALSE;
12411     } else {
12412         /* currentMoveString is set as a side-effect of yylex */
12413
12414         thinkOutput[0] = NULLCHAR;
12415         MakeMove(fromX, fromY, toX, toY, promoChar);
12416         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12417         currentMove = forwardMostMove;
12418         return TRUE;
12419     }
12420 }
12421
12422 /* Load the nth game from the given file */
12423 int
12424 LoadGameFromFile (char *filename, int n, char *title, int useList)
12425 {
12426     FILE *f;
12427     char buf[MSG_SIZ];
12428
12429     if (strcmp(filename, "-") == 0) {
12430         f = stdin;
12431         title = "stdin";
12432     } else {
12433         f = fopen(filename, "rb");
12434         if (f == NULL) {
12435           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12436             DisplayError(buf, errno);
12437             return FALSE;
12438         }
12439     }
12440     if (fseek(f, 0, 0) == -1) {
12441         /* f is not seekable; probably a pipe */
12442         useList = FALSE;
12443     }
12444     if (useList && n == 0) {
12445         int error = GameListBuild(f);
12446         if (error) {
12447             DisplayError(_("Cannot build game list"), error);
12448         } else if (!ListEmpty(&gameList) &&
12449                    ((ListGame *) gameList.tailPred)->number > 1) {
12450             GameListPopUp(f, title);
12451             return TRUE;
12452         }
12453         GameListDestroy();
12454         n = 1;
12455     }
12456     if (n == 0) n = 1;
12457     return LoadGame(f, n, title, FALSE);
12458 }
12459
12460
12461 void
12462 MakeRegisteredMove ()
12463 {
12464     int fromX, fromY, toX, toY;
12465     char promoChar;
12466     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12467         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12468           case CMAIL_MOVE:
12469           case CMAIL_DRAW:
12470             if (appData.debugMode)
12471               fprintf(debugFP, "Restoring %s for game %d\n",
12472                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12473
12474             thinkOutput[0] = NULLCHAR;
12475             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12476             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12477             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12478             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12479             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12480             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12481             MakeMove(fromX, fromY, toX, toY, promoChar);
12482             ShowMove(fromX, fromY, toX, toY);
12483
12484             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12485               case MT_NONE:
12486               case MT_CHECK:
12487                 break;
12488
12489               case MT_CHECKMATE:
12490               case MT_STAINMATE:
12491                 if (WhiteOnMove(currentMove)) {
12492                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12493                 } else {
12494                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12495                 }
12496                 break;
12497
12498               case MT_STALEMATE:
12499                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12500                 break;
12501             }
12502
12503             break;
12504
12505           case CMAIL_RESIGN:
12506             if (WhiteOnMove(currentMove)) {
12507                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12508             } else {
12509                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12510             }
12511             break;
12512
12513           case CMAIL_ACCEPT:
12514             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12515             break;
12516
12517           default:
12518             break;
12519         }
12520     }
12521
12522     return;
12523 }
12524
12525 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12526 int
12527 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12528 {
12529     int retVal;
12530
12531     if (gameNumber > nCmailGames) {
12532         DisplayError(_("No more games in this message"), 0);
12533         return FALSE;
12534     }
12535     if (f == lastLoadGameFP) {
12536         int offset = gameNumber - lastLoadGameNumber;
12537         if (offset == 0) {
12538             cmailMsg[0] = NULLCHAR;
12539             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12540                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12541                 nCmailMovesRegistered--;
12542             }
12543             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12544             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12545                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12546             }
12547         } else {
12548             if (! RegisterMove()) return FALSE;
12549         }
12550     }
12551
12552     retVal = LoadGame(f, gameNumber, title, useList);
12553
12554     /* Make move registered during previous look at this game, if any */
12555     MakeRegisteredMove();
12556
12557     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12558         commentList[currentMove]
12559           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12560         DisplayComment(currentMove - 1, commentList[currentMove]);
12561     }
12562
12563     return retVal;
12564 }
12565
12566 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12567 int
12568 ReloadGame (int offset)
12569 {
12570     int gameNumber = lastLoadGameNumber + offset;
12571     if (lastLoadGameFP == NULL) {
12572         DisplayError(_("No game has been loaded yet"), 0);
12573         return FALSE;
12574     }
12575     if (gameNumber <= 0) {
12576         DisplayError(_("Can't back up any further"), 0);
12577         return FALSE;
12578     }
12579     if (cmailMsgLoaded) {
12580         return CmailLoadGame(lastLoadGameFP, gameNumber,
12581                              lastLoadGameTitle, lastLoadGameUseList);
12582     } else {
12583         return LoadGame(lastLoadGameFP, gameNumber,
12584                         lastLoadGameTitle, lastLoadGameUseList);
12585     }
12586 }
12587
12588 int keys[EmptySquare+1];
12589
12590 int
12591 PositionMatches (Board b1, Board b2)
12592 {
12593     int r, f, sum=0;
12594     switch(appData.searchMode) {
12595         case 1: return CompareWithRights(b1, b2);
12596         case 2:
12597             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12598                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12599             }
12600             return TRUE;
12601         case 3:
12602             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12603               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12604                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12605             }
12606             return sum==0;
12607         case 4:
12608             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12609                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12610             }
12611             return sum==0;
12612     }
12613     return TRUE;
12614 }
12615
12616 #define Q_PROMO  4
12617 #define Q_EP     3
12618 #define Q_BCASTL 2
12619 #define Q_WCASTL 1
12620
12621 int pieceList[256], quickBoard[256];
12622 ChessSquare pieceType[256] = { EmptySquare };
12623 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12624 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12625 int soughtTotal, turn;
12626 Boolean epOK, flipSearch;
12627
12628 typedef struct {
12629     unsigned char piece, to;
12630 } Move;
12631
12632 #define DSIZE (250000)
12633
12634 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12635 Move *moveDatabase = initialSpace;
12636 unsigned int movePtr, dataSize = DSIZE;
12637
12638 int
12639 MakePieceList (Board board, int *counts)
12640 {
12641     int r, f, n=Q_PROMO, total=0;
12642     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12643     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12644         int sq = f + (r<<4);
12645         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12646             quickBoard[sq] = ++n;
12647             pieceList[n] = sq;
12648             pieceType[n] = board[r][f];
12649             counts[board[r][f]]++;
12650             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12651             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12652             total++;
12653         }
12654     }
12655     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12656     return total;
12657 }
12658
12659 void
12660 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12661 {
12662     int sq = fromX + (fromY<<4);
12663     int piece = quickBoard[sq], rook;
12664     quickBoard[sq] = 0;
12665     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12666     if(piece == pieceList[1] && fromY == toY) {
12667       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12668         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12669         moveDatabase[movePtr++].piece = Q_WCASTL;
12670         quickBoard[sq] = piece;
12671         piece = quickBoard[from]; quickBoard[from] = 0;
12672         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12673       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12674         quickBoard[sq] = 0; // remove Rook
12675         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12676         moveDatabase[movePtr++].piece = Q_WCASTL;
12677         quickBoard[sq] = pieceList[1]; // put King
12678         piece = rook;
12679         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12680       }
12681     } else
12682     if(piece == pieceList[2] && fromY == toY) {
12683       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12684         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12685         moveDatabase[movePtr++].piece = Q_BCASTL;
12686         quickBoard[sq] = piece;
12687         piece = quickBoard[from]; quickBoard[from] = 0;
12688         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12689       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12690         quickBoard[sq] = 0; // remove Rook
12691         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12692         moveDatabase[movePtr++].piece = Q_BCASTL;
12693         quickBoard[sq] = pieceList[2]; // put King
12694         piece = rook;
12695         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12696       }
12697     } else
12698     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12699         quickBoard[(fromY<<4)+toX] = 0;
12700         moveDatabase[movePtr].piece = Q_EP;
12701         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12702         moveDatabase[movePtr].to = sq;
12703     } else
12704     if(promoPiece != pieceType[piece]) {
12705         moveDatabase[movePtr++].piece = Q_PROMO;
12706         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12707     }
12708     moveDatabase[movePtr].piece = piece;
12709     quickBoard[sq] = piece;
12710     movePtr++;
12711 }
12712
12713 int
12714 PackGame (Board board)
12715 {
12716     Move *newSpace = NULL;
12717     moveDatabase[movePtr].piece = 0; // terminate previous game
12718     if(movePtr > dataSize) {
12719         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12720         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12721         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12722         if(newSpace) {
12723             int i;
12724             Move *p = moveDatabase, *q = newSpace;
12725             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12726             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12727             moveDatabase = newSpace;
12728         } else { // calloc failed, we must be out of memory. Too bad...
12729             dataSize = 0; // prevent calloc events for all subsequent games
12730             return 0;     // and signal this one isn't cached
12731         }
12732     }
12733     movePtr++;
12734     MakePieceList(board, counts);
12735     return movePtr;
12736 }
12737
12738 int
12739 QuickCompare (Board board, int *minCounts, int *maxCounts)
12740 {   // compare according to search mode
12741     int r, f;
12742     switch(appData.searchMode)
12743     {
12744       case 1: // exact position match
12745         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12746         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12747             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12748         }
12749         break;
12750       case 2: // can have extra material on empty squares
12751         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12752             if(board[r][f] == EmptySquare) continue;
12753             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12754         }
12755         break;
12756       case 3: // material with exact Pawn structure
12757         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12758             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12759             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12760         } // fall through to material comparison
12761       case 4: // exact material
12762         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12763         break;
12764       case 6: // material range with given imbalance
12765         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12766         // fall through to range comparison
12767       case 5: // material range
12768         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12769     }
12770     return TRUE;
12771 }
12772
12773 int
12774 QuickScan (Board board, Move *move)
12775 {   // reconstruct game,and compare all positions in it
12776     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12777     do {
12778         int piece = move->piece;
12779         int to = move->to, from = pieceList[piece];
12780         if(found < 0) { // if already found just scan to game end for final piece count
12781           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12782            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12783            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12784                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12785             ) {
12786             static int lastCounts[EmptySquare+1];
12787             int i;
12788             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12789             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12790           } else stretch = 0;
12791           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12792           if(found >= 0 && !appData.minPieces) return found;
12793         }
12794         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12795           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12796           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12797             piece = (++move)->piece;
12798             from = pieceList[piece];
12799             counts[pieceType[piece]]--;
12800             pieceType[piece] = (ChessSquare) move->to;
12801             counts[move->to]++;
12802           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12803             counts[pieceType[quickBoard[to]]]--;
12804             quickBoard[to] = 0; total--;
12805             move++;
12806             continue;
12807           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12808             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12809             from  = pieceList[piece]; // so this must be King
12810             quickBoard[from] = 0;
12811             pieceList[piece] = to;
12812             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12813             quickBoard[from] = 0; // rook
12814             quickBoard[to] = piece;
12815             to = move->to; piece = move->piece;
12816             goto aftercastle;
12817           }
12818         }
12819         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12820         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12821         quickBoard[from] = 0;
12822       aftercastle:
12823         quickBoard[to] = piece;
12824         pieceList[piece] = to;
12825         cnt++; turn ^= 3;
12826         move++;
12827     } while(1);
12828 }
12829
12830 void
12831 InitSearch ()
12832 {
12833     int r, f;
12834     flipSearch = FALSE;
12835     CopyBoard(soughtBoard, boards[currentMove]);
12836     soughtTotal = MakePieceList(soughtBoard, maxSought);
12837     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12838     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12839     CopyBoard(reverseBoard, boards[currentMove]);
12840     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12841         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12842         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12843         reverseBoard[r][f] = piece;
12844     }
12845     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12846     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12847     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12848                  || (boards[currentMove][CASTLING][2] == NoRights ||
12849                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12850                  && (boards[currentMove][CASTLING][5] == NoRights ||
12851                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12852       ) {
12853         flipSearch = TRUE;
12854         CopyBoard(flipBoard, soughtBoard);
12855         CopyBoard(rotateBoard, reverseBoard);
12856         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12857             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12858             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12859         }
12860     }
12861     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12862     if(appData.searchMode >= 5) {
12863         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12864         MakePieceList(soughtBoard, minSought);
12865         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12866     }
12867     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12868         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12869 }
12870
12871 GameInfo dummyInfo;
12872 static int creatingBook;
12873
12874 int
12875 GameContainsPosition (FILE *f, ListGame *lg)
12876 {
12877     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12878     int fromX, fromY, toX, toY;
12879     char promoChar;
12880     static int initDone=FALSE;
12881
12882     // weed out games based on numerical tag comparison
12883     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12884     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12885     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12886     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12887     if(!initDone) {
12888         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12889         initDone = TRUE;
12890     }
12891     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12892     else CopyBoard(boards[scratch], initialPosition); // default start position
12893     if(lg->moves) {
12894         turn = btm + 1;
12895         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12896         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12897     }
12898     if(btm) plyNr++;
12899     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12900     fseek(f, lg->offset, 0);
12901     yynewfile(f);
12902     while(1) {
12903         yyboardindex = scratch;
12904         quickFlag = plyNr+1;
12905         next = Myylex();
12906         quickFlag = 0;
12907         switch(next) {
12908             case PGNTag:
12909                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12910             default:
12911                 continue;
12912
12913             case XBoardGame:
12914             case GNUChessGame:
12915                 if(plyNr) return -1; // after we have seen moves, this is for new game
12916               continue;
12917
12918             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12919             case ImpossibleMove:
12920             case WhiteWins: // game ends here with these four
12921             case BlackWins:
12922             case GameIsDrawn:
12923             case GameUnfinished:
12924                 return -1;
12925
12926             case IllegalMove:
12927                 if(appData.testLegality) return -1;
12928             case WhiteCapturesEnPassant:
12929             case BlackCapturesEnPassant:
12930             case WhitePromotion:
12931             case BlackPromotion:
12932             case WhiteNonPromotion:
12933             case BlackNonPromotion:
12934             case NormalMove:
12935             case FirstLeg:
12936             case WhiteKingSideCastle:
12937             case WhiteQueenSideCastle:
12938             case BlackKingSideCastle:
12939             case BlackQueenSideCastle:
12940             case WhiteKingSideCastleWild:
12941             case WhiteQueenSideCastleWild:
12942             case BlackKingSideCastleWild:
12943             case BlackQueenSideCastleWild:
12944             case WhiteHSideCastleFR:
12945             case WhiteASideCastleFR:
12946             case BlackHSideCastleFR:
12947             case BlackASideCastleFR:
12948                 fromX = currentMoveString[0] - AAA;
12949                 fromY = currentMoveString[1] - ONE;
12950                 toX = currentMoveString[2] - AAA;
12951                 toY = currentMoveString[3] - ONE;
12952                 promoChar = currentMoveString[4];
12953                 break;
12954             case WhiteDrop:
12955             case BlackDrop:
12956                 fromX = next == WhiteDrop ?
12957                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12958                   (int) CharToPiece(ToLower(currentMoveString[0]));
12959                 fromY = DROP_RANK;
12960                 toX = currentMoveString[2] - AAA;
12961                 toY = currentMoveString[3] - ONE;
12962                 promoChar = 0;
12963                 break;
12964         }
12965         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12966         plyNr++;
12967         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12968         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12969         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12970         if(appData.findMirror) {
12971             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12972             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12973         }
12974     }
12975 }
12976
12977 /* Load the nth game from open file f */
12978 int
12979 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12980 {
12981     ChessMove cm;
12982     char buf[MSG_SIZ];
12983     int gn = gameNumber;
12984     ListGame *lg = NULL;
12985     int numPGNTags = 0, i;
12986     int err, pos = -1;
12987     GameMode oldGameMode;
12988     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12989     char oldName[MSG_SIZ];
12990
12991     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12992
12993     if (appData.debugMode)
12994         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12995
12996     if (gameMode == Training )
12997         SetTrainingModeOff();
12998
12999     oldGameMode = gameMode;
13000     if (gameMode != BeginningOfGame) {
13001       Reset(FALSE, TRUE);
13002     }
13003     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13004
13005     gameFileFP = f;
13006     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13007         fclose(lastLoadGameFP);
13008     }
13009
13010     if (useList) {
13011         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13012
13013         if (lg) {
13014             fseek(f, lg->offset, 0);
13015             GameListHighlight(gameNumber);
13016             pos = lg->position;
13017             gn = 1;
13018         }
13019         else {
13020             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13021               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13022             else
13023             DisplayError(_("Game number out of range"), 0);
13024             return FALSE;
13025         }
13026     } else {
13027         GameListDestroy();
13028         if (fseek(f, 0, 0) == -1) {
13029             if (f == lastLoadGameFP ?
13030                 gameNumber == lastLoadGameNumber + 1 :
13031                 gameNumber == 1) {
13032                 gn = 1;
13033             } else {
13034                 DisplayError(_("Can't seek on game file"), 0);
13035                 return FALSE;
13036             }
13037         }
13038     }
13039     lastLoadGameFP = f;
13040     lastLoadGameNumber = gameNumber;
13041     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13042     lastLoadGameUseList = useList;
13043
13044     yynewfile(f);
13045
13046     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13047       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13048                 lg->gameInfo.black);
13049             DisplayTitle(buf);
13050     } else if (*title != NULLCHAR) {
13051         if (gameNumber > 1) {
13052           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13053             DisplayTitle(buf);
13054         } else {
13055             DisplayTitle(title);
13056         }
13057     }
13058
13059     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13060         gameMode = PlayFromGameFile;
13061         ModeHighlight();
13062     }
13063
13064     currentMove = forwardMostMove = backwardMostMove = 0;
13065     CopyBoard(boards[0], initialPosition);
13066     StopClocks();
13067
13068     /*
13069      * Skip the first gn-1 games in the file.
13070      * Also skip over anything that precedes an identifiable
13071      * start of game marker, to avoid being confused by
13072      * garbage at the start of the file.  Currently
13073      * recognized start of game markers are the move number "1",
13074      * the pattern "gnuchess .* game", the pattern
13075      * "^[#;%] [^ ]* game file", and a PGN tag block.
13076      * A game that starts with one of the latter two patterns
13077      * will also have a move number 1, possibly
13078      * following a position diagram.
13079      * 5-4-02: Let's try being more lenient and allowing a game to
13080      * start with an unnumbered move.  Does that break anything?
13081      */
13082     cm = lastLoadGameStart = EndOfFile;
13083     while (gn > 0) {
13084         yyboardindex = forwardMostMove;
13085         cm = (ChessMove) Myylex();
13086         switch (cm) {
13087           case EndOfFile:
13088             if (cmailMsgLoaded) {
13089                 nCmailGames = CMAIL_MAX_GAMES - gn;
13090             } else {
13091                 Reset(TRUE, TRUE);
13092                 DisplayError(_("Game not found in file"), 0);
13093             }
13094             return FALSE;
13095
13096           case GNUChessGame:
13097           case XBoardGame:
13098             gn--;
13099             lastLoadGameStart = cm;
13100             break;
13101
13102           case MoveNumberOne:
13103             switch (lastLoadGameStart) {
13104               case GNUChessGame:
13105               case XBoardGame:
13106               case PGNTag:
13107                 break;
13108               case MoveNumberOne:
13109               case EndOfFile:
13110                 gn--;           /* count this game */
13111                 lastLoadGameStart = cm;
13112                 break;
13113               default:
13114                 /* impossible */
13115                 break;
13116             }
13117             break;
13118
13119           case PGNTag:
13120             switch (lastLoadGameStart) {
13121               case GNUChessGame:
13122               case PGNTag:
13123               case MoveNumberOne:
13124               case EndOfFile:
13125                 gn--;           /* count this game */
13126                 lastLoadGameStart = cm;
13127                 break;
13128               case XBoardGame:
13129                 lastLoadGameStart = cm; /* game counted already */
13130                 break;
13131               default:
13132                 /* impossible */
13133                 break;
13134             }
13135             if (gn > 0) {
13136                 do {
13137                     yyboardindex = forwardMostMove;
13138                     cm = (ChessMove) Myylex();
13139                 } while (cm == PGNTag || cm == Comment);
13140             }
13141             break;
13142
13143           case WhiteWins:
13144           case BlackWins:
13145           case GameIsDrawn:
13146             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13147                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13148                     != CMAIL_OLD_RESULT) {
13149                     nCmailResults ++ ;
13150                     cmailResult[  CMAIL_MAX_GAMES
13151                                 - gn - 1] = CMAIL_OLD_RESULT;
13152                 }
13153             }
13154             break;
13155
13156           case NormalMove:
13157           case FirstLeg:
13158             /* Only a NormalMove can be at the start of a game
13159              * without a position diagram. */
13160             if (lastLoadGameStart == EndOfFile ) {
13161               gn--;
13162               lastLoadGameStart = MoveNumberOne;
13163             }
13164             break;
13165
13166           default:
13167             break;
13168         }
13169     }
13170
13171     if (appData.debugMode)
13172       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13173
13174     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13175
13176     if (cm == XBoardGame) {
13177         /* Skip any header junk before position diagram and/or move 1 */
13178         for (;;) {
13179             yyboardindex = forwardMostMove;
13180             cm = (ChessMove) Myylex();
13181
13182             if (cm == EndOfFile ||
13183                 cm == GNUChessGame || cm == XBoardGame) {
13184                 /* Empty game; pretend end-of-file and handle later */
13185                 cm = EndOfFile;
13186                 break;
13187             }
13188
13189             if (cm == MoveNumberOne || cm == PositionDiagram ||
13190                 cm == PGNTag || cm == Comment)
13191               break;
13192         }
13193     } else if (cm == GNUChessGame) {
13194         if (gameInfo.event != NULL) {
13195             free(gameInfo.event);
13196         }
13197         gameInfo.event = StrSave(yy_text);
13198     }
13199
13200     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13201     while (cm == PGNTag) {
13202         if (appData.debugMode)
13203           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13204         err = ParsePGNTag(yy_text, &gameInfo);
13205         if (!err) numPGNTags++;
13206
13207         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13208         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13209             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13210             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13211             InitPosition(TRUE);
13212             oldVariant = gameInfo.variant;
13213             if (appData.debugMode)
13214               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13215         }
13216
13217
13218         if (gameInfo.fen != NULL) {
13219           Board initial_position;
13220           startedFromSetupPosition = TRUE;
13221           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13222             Reset(TRUE, TRUE);
13223             DisplayError(_("Bad FEN position in file"), 0);
13224             return FALSE;
13225           }
13226           CopyBoard(boards[0], initial_position);
13227           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13228             CopyBoard(initialPosition, initial_position);
13229           if (blackPlaysFirst) {
13230             currentMove = forwardMostMove = backwardMostMove = 1;
13231             CopyBoard(boards[1], initial_position);
13232             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13233             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13234             timeRemaining[0][1] = whiteTimeRemaining;
13235             timeRemaining[1][1] = blackTimeRemaining;
13236             if (commentList[0] != NULL) {
13237               commentList[1] = commentList[0];
13238               commentList[0] = NULL;
13239             }
13240           } else {
13241             currentMove = forwardMostMove = backwardMostMove = 0;
13242           }
13243           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13244           {   int i;
13245               initialRulePlies = FENrulePlies;
13246               for( i=0; i< nrCastlingRights; i++ )
13247                   initialRights[i] = initial_position[CASTLING][i];
13248           }
13249           yyboardindex = forwardMostMove;
13250           free(gameInfo.fen);
13251           gameInfo.fen = NULL;
13252         }
13253
13254         yyboardindex = forwardMostMove;
13255         cm = (ChessMove) Myylex();
13256
13257         /* Handle comments interspersed among the tags */
13258         while (cm == Comment) {
13259             char *p;
13260             if (appData.debugMode)
13261               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13262             p = yy_text;
13263             AppendComment(currentMove, p, FALSE);
13264             yyboardindex = forwardMostMove;
13265             cm = (ChessMove) Myylex();
13266         }
13267     }
13268
13269     /* don't rely on existence of Event tag since if game was
13270      * pasted from clipboard the Event tag may not exist
13271      */
13272     if (numPGNTags > 0){
13273         char *tags;
13274         if (gameInfo.variant == VariantNormal) {
13275           VariantClass v = StringToVariant(gameInfo.event);
13276           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13277           if(v < VariantShogi) gameInfo.variant = v;
13278         }
13279         if (!matchMode) {
13280           if( appData.autoDisplayTags ) {
13281             tags = PGNTags(&gameInfo);
13282             TagsPopUp(tags, CmailMsg());
13283             free(tags);
13284           }
13285         }
13286     } else {
13287         /* Make something up, but don't display it now */
13288         SetGameInfo();
13289         TagsPopDown();
13290     }
13291
13292     if (cm == PositionDiagram) {
13293         int i, j;
13294         char *p;
13295         Board initial_position;
13296
13297         if (appData.debugMode)
13298           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13299
13300         if (!startedFromSetupPosition) {
13301             p = yy_text;
13302             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13303               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13304                 switch (*p) {
13305                   case '{':
13306                   case '[':
13307                   case '-':
13308                   case ' ':
13309                   case '\t':
13310                   case '\n':
13311                   case '\r':
13312                     break;
13313                   default:
13314                     initial_position[i][j++] = CharToPiece(*p);
13315                     break;
13316                 }
13317             while (*p == ' ' || *p == '\t' ||
13318                    *p == '\n' || *p == '\r') p++;
13319
13320             if (strncmp(p, "black", strlen("black"))==0)
13321               blackPlaysFirst = TRUE;
13322             else
13323               blackPlaysFirst = FALSE;
13324             startedFromSetupPosition = TRUE;
13325
13326             CopyBoard(boards[0], initial_position);
13327             if (blackPlaysFirst) {
13328                 currentMove = forwardMostMove = backwardMostMove = 1;
13329                 CopyBoard(boards[1], initial_position);
13330                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13331                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13332                 timeRemaining[0][1] = whiteTimeRemaining;
13333                 timeRemaining[1][1] = blackTimeRemaining;
13334                 if (commentList[0] != NULL) {
13335                     commentList[1] = commentList[0];
13336                     commentList[0] = NULL;
13337                 }
13338             } else {
13339                 currentMove = forwardMostMove = backwardMostMove = 0;
13340             }
13341         }
13342         yyboardindex = forwardMostMove;
13343         cm = (ChessMove) Myylex();
13344     }
13345
13346   if(!creatingBook) {
13347     if (first.pr == NoProc) {
13348         StartChessProgram(&first);
13349     }
13350     InitChessProgram(&first, FALSE);
13351     if(gameInfo.variant == VariantUnknown && *oldName) {
13352         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13353         gameInfo.variant = v;
13354     }
13355     SendToProgram("force\n", &first);
13356     if (startedFromSetupPosition) {
13357         SendBoard(&first, forwardMostMove);
13358     if (appData.debugMode) {
13359         fprintf(debugFP, "Load Game\n");
13360     }
13361         DisplayBothClocks();
13362     }
13363   }
13364
13365     /* [HGM] server: flag to write setup moves in broadcast file as one */
13366     loadFlag = appData.suppressLoadMoves;
13367
13368     while (cm == Comment) {
13369         char *p;
13370         if (appData.debugMode)
13371           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13372         p = yy_text;
13373         AppendComment(currentMove, p, FALSE);
13374         yyboardindex = forwardMostMove;
13375         cm = (ChessMove) Myylex();
13376     }
13377
13378     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13379         cm == WhiteWins || cm == BlackWins ||
13380         cm == GameIsDrawn || cm == GameUnfinished) {
13381         DisplayMessage("", _("No moves in game"));
13382         if (cmailMsgLoaded) {
13383             if (appData.debugMode)
13384               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13385             ClearHighlights();
13386             flipView = FALSE;
13387         }
13388         DrawPosition(FALSE, boards[currentMove]);
13389         DisplayBothClocks();
13390         gameMode = EditGame;
13391         ModeHighlight();
13392         gameFileFP = NULL;
13393         cmailOldMove = 0;
13394         return TRUE;
13395     }
13396
13397     // [HGM] PV info: routine tests if comment empty
13398     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13399         DisplayComment(currentMove - 1, commentList[currentMove]);
13400     }
13401     if (!matchMode && appData.timeDelay != 0)
13402       DrawPosition(FALSE, boards[currentMove]);
13403
13404     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13405       programStats.ok_to_send = 1;
13406     }
13407
13408     /* if the first token after the PGN tags is a move
13409      * and not move number 1, retrieve it from the parser
13410      */
13411     if (cm != MoveNumberOne)
13412         LoadGameOneMove(cm);
13413
13414     /* load the remaining moves from the file */
13415     while (LoadGameOneMove(EndOfFile)) {
13416       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13417       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13418     }
13419
13420     /* rewind to the start of the game */
13421     currentMove = backwardMostMove;
13422
13423     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13424
13425     if (oldGameMode == AnalyzeFile) {
13426       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13427       AnalyzeFileEvent();
13428     } else
13429     if (oldGameMode == AnalyzeMode) {
13430       AnalyzeFileEvent();
13431     }
13432
13433     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13434         long int w, b; // [HGM] adjourn: restore saved clock times
13435         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13436         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13437             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13438             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13439         }
13440     }
13441
13442     if(creatingBook) return TRUE;
13443     if (!matchMode && pos > 0) {
13444         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13445     } else
13446     if (matchMode || appData.timeDelay == 0) {
13447       ToEndEvent();
13448     } else if (appData.timeDelay > 0) {
13449       AutoPlayGameLoop();
13450     }
13451
13452     if (appData.debugMode)
13453         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13454
13455     loadFlag = 0; /* [HGM] true game starts */
13456     return TRUE;
13457 }
13458
13459 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13460 int
13461 ReloadPosition (int offset)
13462 {
13463     int positionNumber = lastLoadPositionNumber + offset;
13464     if (lastLoadPositionFP == NULL) {
13465         DisplayError(_("No position has been loaded yet"), 0);
13466         return FALSE;
13467     }
13468     if (positionNumber <= 0) {
13469         DisplayError(_("Can't back up any further"), 0);
13470         return FALSE;
13471     }
13472     return LoadPosition(lastLoadPositionFP, positionNumber,
13473                         lastLoadPositionTitle);
13474 }
13475
13476 /* Load the nth position from the given file */
13477 int
13478 LoadPositionFromFile (char *filename, int n, char *title)
13479 {
13480     FILE *f;
13481     char buf[MSG_SIZ];
13482
13483     if (strcmp(filename, "-") == 0) {
13484         return LoadPosition(stdin, n, "stdin");
13485     } else {
13486         f = fopen(filename, "rb");
13487         if (f == NULL) {
13488             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13489             DisplayError(buf, errno);
13490             return FALSE;
13491         } else {
13492             return LoadPosition(f, n, title);
13493         }
13494     }
13495 }
13496
13497 /* Load the nth position from the given open file, and close it */
13498 int
13499 LoadPosition (FILE *f, int positionNumber, char *title)
13500 {
13501     char *p, line[MSG_SIZ];
13502     Board initial_position;
13503     int i, j, fenMode, pn;
13504
13505     if (gameMode == Training )
13506         SetTrainingModeOff();
13507
13508     if (gameMode != BeginningOfGame) {
13509         Reset(FALSE, TRUE);
13510     }
13511     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13512         fclose(lastLoadPositionFP);
13513     }
13514     if (positionNumber == 0) positionNumber = 1;
13515     lastLoadPositionFP = f;
13516     lastLoadPositionNumber = positionNumber;
13517     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13518     if (first.pr == NoProc && !appData.noChessProgram) {
13519       StartChessProgram(&first);
13520       InitChessProgram(&first, FALSE);
13521     }
13522     pn = positionNumber;
13523     if (positionNumber < 0) {
13524         /* Negative position number means to seek to that byte offset */
13525         if (fseek(f, -positionNumber, 0) == -1) {
13526             DisplayError(_("Can't seek on position file"), 0);
13527             return FALSE;
13528         };
13529         pn = 1;
13530     } else {
13531         if (fseek(f, 0, 0) == -1) {
13532             if (f == lastLoadPositionFP ?
13533                 positionNumber == lastLoadPositionNumber + 1 :
13534                 positionNumber == 1) {
13535                 pn = 1;
13536             } else {
13537                 DisplayError(_("Can't seek on position file"), 0);
13538                 return FALSE;
13539             }
13540         }
13541     }
13542     /* See if this file is FEN or old-style xboard */
13543     if (fgets(line, MSG_SIZ, f) == NULL) {
13544         DisplayError(_("Position not found in file"), 0);
13545         return FALSE;
13546     }
13547     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13548     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13549
13550     if (pn >= 2) {
13551         if (fenMode || line[0] == '#') pn--;
13552         while (pn > 0) {
13553             /* skip positions before number pn */
13554             if (fgets(line, MSG_SIZ, f) == NULL) {
13555                 Reset(TRUE, TRUE);
13556                 DisplayError(_("Position not found in file"), 0);
13557                 return FALSE;
13558             }
13559             if (fenMode || line[0] == '#') pn--;
13560         }
13561     }
13562
13563     if (fenMode) {
13564         char *p;
13565         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13566             DisplayError(_("Bad FEN position in file"), 0);
13567             return FALSE;
13568         }
13569         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13570             sscanf(p+4, "%[^;]", bestMove);
13571         } else *bestMove = NULLCHAR;
13572         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13573             sscanf(p+4, "%[^;]", avoidMove);
13574         } else *avoidMove = NULLCHAR;
13575     } else {
13576         (void) fgets(line, MSG_SIZ, f);
13577         (void) fgets(line, MSG_SIZ, f);
13578
13579         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13580             (void) fgets(line, MSG_SIZ, f);
13581             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13582                 if (*p == ' ')
13583                   continue;
13584                 initial_position[i][j++] = CharToPiece(*p);
13585             }
13586         }
13587
13588         blackPlaysFirst = FALSE;
13589         if (!feof(f)) {
13590             (void) fgets(line, MSG_SIZ, f);
13591             if (strncmp(line, "black", strlen("black"))==0)
13592               blackPlaysFirst = TRUE;
13593         }
13594     }
13595     startedFromSetupPosition = TRUE;
13596
13597     CopyBoard(boards[0], initial_position);
13598     if (blackPlaysFirst) {
13599         currentMove = forwardMostMove = backwardMostMove = 1;
13600         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13601         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13602         CopyBoard(boards[1], initial_position);
13603         DisplayMessage("", _("Black to play"));
13604     } else {
13605         currentMove = forwardMostMove = backwardMostMove = 0;
13606         DisplayMessage("", _("White to play"));
13607     }
13608     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13609     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13610         SendToProgram("force\n", &first);
13611         SendBoard(&first, forwardMostMove);
13612     }
13613     if (appData.debugMode) {
13614 int i, j;
13615   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13616   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13617         fprintf(debugFP, "Load Position\n");
13618     }
13619
13620     if (positionNumber > 1) {
13621       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13622         DisplayTitle(line);
13623     } else {
13624         DisplayTitle(title);
13625     }
13626     gameMode = EditGame;
13627     ModeHighlight();
13628     ResetClocks();
13629     timeRemaining[0][1] = whiteTimeRemaining;
13630     timeRemaining[1][1] = blackTimeRemaining;
13631     DrawPosition(FALSE, boards[currentMove]);
13632
13633     return TRUE;
13634 }
13635
13636
13637 void
13638 CopyPlayerNameIntoFileName (char **dest, char *src)
13639 {
13640     while (*src != NULLCHAR && *src != ',') {
13641         if (*src == ' ') {
13642             *(*dest)++ = '_';
13643             src++;
13644         } else {
13645             *(*dest)++ = *src++;
13646         }
13647     }
13648 }
13649
13650 char *
13651 DefaultFileName (char *ext)
13652 {
13653     static char def[MSG_SIZ];
13654     char *p;
13655
13656     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13657         p = def;
13658         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13659         *p++ = '-';
13660         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13661         *p++ = '.';
13662         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13663     } else {
13664         def[0] = NULLCHAR;
13665     }
13666     return def;
13667 }
13668
13669 /* Save the current game to the given file */
13670 int
13671 SaveGameToFile (char *filename, int append)
13672 {
13673     FILE *f;
13674     char buf[MSG_SIZ];
13675     int result, i, t,tot=0;
13676
13677     if (strcmp(filename, "-") == 0) {
13678         return SaveGame(stdout, 0, NULL);
13679     } else {
13680         for(i=0; i<10; i++) { // upto 10 tries
13681              f = fopen(filename, append ? "a" : "w");
13682              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13683              if(f || errno != 13) break;
13684              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13685              tot += t;
13686         }
13687         if (f == NULL) {
13688             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13689             DisplayError(buf, errno);
13690             return FALSE;
13691         } else {
13692             safeStrCpy(buf, lastMsg, MSG_SIZ);
13693             DisplayMessage(_("Waiting for access to save file"), "");
13694             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13695             DisplayMessage(_("Saving game"), "");
13696             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13697             result = SaveGame(f, 0, NULL);
13698             DisplayMessage(buf, "");
13699             return result;
13700         }
13701     }
13702 }
13703
13704 char *
13705 SavePart (char *str)
13706 {
13707     static char buf[MSG_SIZ];
13708     char *p;
13709
13710     p = strchr(str, ' ');
13711     if (p == NULL) return str;
13712     strncpy(buf, str, p - str);
13713     buf[p - str] = NULLCHAR;
13714     return buf;
13715 }
13716
13717 #define PGN_MAX_LINE 75
13718
13719 #define PGN_SIDE_WHITE  0
13720 #define PGN_SIDE_BLACK  1
13721
13722 static int
13723 FindFirstMoveOutOfBook (int side)
13724 {
13725     int result = -1;
13726
13727     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13728         int index = backwardMostMove;
13729         int has_book_hit = 0;
13730
13731         if( (index % 2) != side ) {
13732             index++;
13733         }
13734
13735         while( index < forwardMostMove ) {
13736             /* Check to see if engine is in book */
13737             int depth = pvInfoList[index].depth;
13738             int score = pvInfoList[index].score;
13739             int in_book = 0;
13740
13741             if( depth <= 2 ) {
13742                 in_book = 1;
13743             }
13744             else if( score == 0 && depth == 63 ) {
13745                 in_book = 1; /* Zappa */
13746             }
13747             else if( score == 2 && depth == 99 ) {
13748                 in_book = 1; /* Abrok */
13749             }
13750
13751             has_book_hit += in_book;
13752
13753             if( ! in_book ) {
13754                 result = index;
13755
13756                 break;
13757             }
13758
13759             index += 2;
13760         }
13761     }
13762
13763     return result;
13764 }
13765
13766 void
13767 GetOutOfBookInfo (char * buf)
13768 {
13769     int oob[2];
13770     int i;
13771     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13772
13773     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13774     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13775
13776     *buf = '\0';
13777
13778     if( oob[0] >= 0 || oob[1] >= 0 ) {
13779         for( i=0; i<2; i++ ) {
13780             int idx = oob[i];
13781
13782             if( idx >= 0 ) {
13783                 if( i > 0 && oob[0] >= 0 ) {
13784                     strcat( buf, "   " );
13785                 }
13786
13787                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13788                 sprintf( buf+strlen(buf), "%s%.2f",
13789                     pvInfoList[idx].score >= 0 ? "+" : "",
13790                     pvInfoList[idx].score / 100.0 );
13791             }
13792         }
13793     }
13794 }
13795
13796 /* Save game in PGN style */
13797 static void
13798 SaveGamePGN2 (FILE *f)
13799 {
13800     int i, offset, linelen, newblock;
13801 //    char *movetext;
13802     char numtext[32];
13803     int movelen, numlen, blank;
13804     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13805
13806     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13807
13808     PrintPGNTags(f, &gameInfo);
13809
13810     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13811
13812     if (backwardMostMove > 0 || startedFromSetupPosition) {
13813         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13814         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13815         fprintf(f, "\n{--------------\n");
13816         PrintPosition(f, backwardMostMove);
13817         fprintf(f, "--------------}\n");
13818         free(fen);
13819     }
13820     else {
13821         /* [AS] Out of book annotation */
13822         if( appData.saveOutOfBookInfo ) {
13823             char buf[64];
13824
13825             GetOutOfBookInfo( buf );
13826
13827             if( buf[0] != '\0' ) {
13828                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13829             }
13830         }
13831
13832         fprintf(f, "\n");
13833     }
13834
13835     i = backwardMostMove;
13836     linelen = 0;
13837     newblock = TRUE;
13838
13839     while (i < forwardMostMove) {
13840         /* Print comments preceding this move */
13841         if (commentList[i] != NULL) {
13842             if (linelen > 0) fprintf(f, "\n");
13843             fprintf(f, "%s", commentList[i]);
13844             linelen = 0;
13845             newblock = TRUE;
13846         }
13847
13848         /* Format move number */
13849         if ((i % 2) == 0)
13850           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13851         else
13852           if (newblock)
13853             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13854           else
13855             numtext[0] = NULLCHAR;
13856
13857         numlen = strlen(numtext);
13858         newblock = FALSE;
13859
13860         /* Print move number */
13861         blank = linelen > 0 && numlen > 0;
13862         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13863             fprintf(f, "\n");
13864             linelen = 0;
13865             blank = 0;
13866         }
13867         if (blank) {
13868             fprintf(f, " ");
13869             linelen++;
13870         }
13871         fprintf(f, "%s", numtext);
13872         linelen += numlen;
13873
13874         /* Get move */
13875         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13876         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13877
13878         /* Print move */
13879         blank = linelen > 0 && movelen > 0;
13880         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13881             fprintf(f, "\n");
13882             linelen = 0;
13883             blank = 0;
13884         }
13885         if (blank) {
13886             fprintf(f, " ");
13887             linelen++;
13888         }
13889         fprintf(f, "%s", move_buffer);
13890         linelen += movelen;
13891
13892         /* [AS] Add PV info if present */
13893         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13894             /* [HGM] add time */
13895             char buf[MSG_SIZ]; int seconds;
13896
13897             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13898
13899             if( seconds <= 0)
13900               buf[0] = 0;
13901             else
13902               if( seconds < 30 )
13903                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13904               else
13905                 {
13906                   seconds = (seconds + 4)/10; // round to full seconds
13907                   if( seconds < 60 )
13908                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13909                   else
13910                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13911                 }
13912
13913             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13914                       pvInfoList[i].score >= 0 ? "+" : "",
13915                       pvInfoList[i].score / 100.0,
13916                       pvInfoList[i].depth,
13917                       buf );
13918
13919             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13920
13921             /* Print score/depth */
13922             blank = linelen > 0 && movelen > 0;
13923             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13924                 fprintf(f, "\n");
13925                 linelen = 0;
13926                 blank = 0;
13927             }
13928             if (blank) {
13929                 fprintf(f, " ");
13930                 linelen++;
13931             }
13932             fprintf(f, "%s", move_buffer);
13933             linelen += movelen;
13934         }
13935
13936         i++;
13937     }
13938
13939     /* Start a new line */
13940     if (linelen > 0) fprintf(f, "\n");
13941
13942     /* Print comments after last move */
13943     if (commentList[i] != NULL) {
13944         fprintf(f, "%s\n", commentList[i]);
13945     }
13946
13947     /* Print result */
13948     if (gameInfo.resultDetails != NULL &&
13949         gameInfo.resultDetails[0] != NULLCHAR) {
13950         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13951         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13952            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13953             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13954         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13955     } else {
13956         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13957     }
13958 }
13959
13960 /* Save game in PGN style and close the file */
13961 int
13962 SaveGamePGN (FILE *f)
13963 {
13964     SaveGamePGN2(f);
13965     fclose(f);
13966     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13967     return TRUE;
13968 }
13969
13970 /* Save game in old style and close the file */
13971 int
13972 SaveGameOldStyle (FILE *f)
13973 {
13974     int i, offset;
13975     time_t tm;
13976
13977     tm = time((time_t *) NULL);
13978
13979     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13980     PrintOpponents(f);
13981
13982     if (backwardMostMove > 0 || startedFromSetupPosition) {
13983         fprintf(f, "\n[--------------\n");
13984         PrintPosition(f, backwardMostMove);
13985         fprintf(f, "--------------]\n");
13986     } else {
13987         fprintf(f, "\n");
13988     }
13989
13990     i = backwardMostMove;
13991     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13992
13993     while (i < forwardMostMove) {
13994         if (commentList[i] != NULL) {
13995             fprintf(f, "[%s]\n", commentList[i]);
13996         }
13997
13998         if ((i % 2) == 1) {
13999             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14000             i++;
14001         } else {
14002             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14003             i++;
14004             if (commentList[i] != NULL) {
14005                 fprintf(f, "\n");
14006                 continue;
14007             }
14008             if (i >= forwardMostMove) {
14009                 fprintf(f, "\n");
14010                 break;
14011             }
14012             fprintf(f, "%s\n", parseList[i]);
14013             i++;
14014         }
14015     }
14016
14017     if (commentList[i] != NULL) {
14018         fprintf(f, "[%s]\n", commentList[i]);
14019     }
14020
14021     /* This isn't really the old style, but it's close enough */
14022     if (gameInfo.resultDetails != NULL &&
14023         gameInfo.resultDetails[0] != NULLCHAR) {
14024         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14025                 gameInfo.resultDetails);
14026     } else {
14027         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14028     }
14029
14030     fclose(f);
14031     return TRUE;
14032 }
14033
14034 /* Save the current game to open file f and close the file */
14035 int
14036 SaveGame (FILE *f, int dummy, char *dummy2)
14037 {
14038     if (gameMode == EditPosition) EditPositionDone(TRUE);
14039     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14040     if (appData.oldSaveStyle)
14041       return SaveGameOldStyle(f);
14042     else
14043       return SaveGamePGN(f);
14044 }
14045
14046 /* Save the current position to the given file */
14047 int
14048 SavePositionToFile (char *filename)
14049 {
14050     FILE *f;
14051     char buf[MSG_SIZ];
14052
14053     if (strcmp(filename, "-") == 0) {
14054         return SavePosition(stdout, 0, NULL);
14055     } else {
14056         f = fopen(filename, "a");
14057         if (f == NULL) {
14058             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14059             DisplayError(buf, errno);
14060             return FALSE;
14061         } else {
14062             safeStrCpy(buf, lastMsg, MSG_SIZ);
14063             DisplayMessage(_("Waiting for access to save file"), "");
14064             flock(fileno(f), LOCK_EX); // [HGM] lock
14065             DisplayMessage(_("Saving position"), "");
14066             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14067             SavePosition(f, 0, NULL);
14068             DisplayMessage(buf, "");
14069             return TRUE;
14070         }
14071     }
14072 }
14073
14074 /* Save the current position to the given open file and close the file */
14075 int
14076 SavePosition (FILE *f, int dummy, char *dummy2)
14077 {
14078     time_t tm;
14079     char *fen;
14080
14081     if (gameMode == EditPosition) EditPositionDone(TRUE);
14082     if (appData.oldSaveStyle) {
14083         tm = time((time_t *) NULL);
14084
14085         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14086         PrintOpponents(f);
14087         fprintf(f, "[--------------\n");
14088         PrintPosition(f, currentMove);
14089         fprintf(f, "--------------]\n");
14090     } else {
14091         fen = PositionToFEN(currentMove, NULL, 1);
14092         fprintf(f, "%s\n", fen);
14093         free(fen);
14094     }
14095     fclose(f);
14096     return TRUE;
14097 }
14098
14099 void
14100 ReloadCmailMsgEvent (int unregister)
14101 {
14102 #if !WIN32
14103     static char *inFilename = NULL;
14104     static char *outFilename;
14105     int i;
14106     struct stat inbuf, outbuf;
14107     int status;
14108
14109     /* Any registered moves are unregistered if unregister is set, */
14110     /* i.e. invoked by the signal handler */
14111     if (unregister) {
14112         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14113             cmailMoveRegistered[i] = FALSE;
14114             if (cmailCommentList[i] != NULL) {
14115                 free(cmailCommentList[i]);
14116                 cmailCommentList[i] = NULL;
14117             }
14118         }
14119         nCmailMovesRegistered = 0;
14120     }
14121
14122     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14123         cmailResult[i] = CMAIL_NOT_RESULT;
14124     }
14125     nCmailResults = 0;
14126
14127     if (inFilename == NULL) {
14128         /* Because the filenames are static they only get malloced once  */
14129         /* and they never get freed                                      */
14130         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14131         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14132
14133         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14134         sprintf(outFilename, "%s.out", appData.cmailGameName);
14135     }
14136
14137     status = stat(outFilename, &outbuf);
14138     if (status < 0) {
14139         cmailMailedMove = FALSE;
14140     } else {
14141         status = stat(inFilename, &inbuf);
14142         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14143     }
14144
14145     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14146        counts the games, notes how each one terminated, etc.
14147
14148        It would be nice to remove this kludge and instead gather all
14149        the information while building the game list.  (And to keep it
14150        in the game list nodes instead of having a bunch of fixed-size
14151        parallel arrays.)  Note this will require getting each game's
14152        termination from the PGN tags, as the game list builder does
14153        not process the game moves.  --mann
14154        */
14155     cmailMsgLoaded = TRUE;
14156     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14157
14158     /* Load first game in the file or popup game menu */
14159     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14160
14161 #endif /* !WIN32 */
14162     return;
14163 }
14164
14165 int
14166 RegisterMove ()
14167 {
14168     FILE *f;
14169     char string[MSG_SIZ];
14170
14171     if (   cmailMailedMove
14172         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14173         return TRUE;            /* Allow free viewing  */
14174     }
14175
14176     /* Unregister move to ensure that we don't leave RegisterMove        */
14177     /* with the move registered when the conditions for registering no   */
14178     /* longer hold                                                       */
14179     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14180         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14181         nCmailMovesRegistered --;
14182
14183         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14184           {
14185               free(cmailCommentList[lastLoadGameNumber - 1]);
14186               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14187           }
14188     }
14189
14190     if (cmailOldMove == -1) {
14191         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14192         return FALSE;
14193     }
14194
14195     if (currentMove > cmailOldMove + 1) {
14196         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14197         return FALSE;
14198     }
14199
14200     if (currentMove < cmailOldMove) {
14201         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14202         return FALSE;
14203     }
14204
14205     if (forwardMostMove > currentMove) {
14206         /* Silently truncate extra moves */
14207         TruncateGame();
14208     }
14209
14210     if (   (currentMove == cmailOldMove + 1)
14211         || (   (currentMove == cmailOldMove)
14212             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14213                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14214         if (gameInfo.result != GameUnfinished) {
14215             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14216         }
14217
14218         if (commentList[currentMove] != NULL) {
14219             cmailCommentList[lastLoadGameNumber - 1]
14220               = StrSave(commentList[currentMove]);
14221         }
14222         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14223
14224         if (appData.debugMode)
14225           fprintf(debugFP, "Saving %s for game %d\n",
14226                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14227
14228         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14229
14230         f = fopen(string, "w");
14231         if (appData.oldSaveStyle) {
14232             SaveGameOldStyle(f); /* also closes the file */
14233
14234             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14235             f = fopen(string, "w");
14236             SavePosition(f, 0, NULL); /* also closes the file */
14237         } else {
14238             fprintf(f, "{--------------\n");
14239             PrintPosition(f, currentMove);
14240             fprintf(f, "--------------}\n\n");
14241
14242             SaveGame(f, 0, NULL); /* also closes the file*/
14243         }
14244
14245         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14246         nCmailMovesRegistered ++;
14247     } else if (nCmailGames == 1) {
14248         DisplayError(_("You have not made a move yet"), 0);
14249         return FALSE;
14250     }
14251
14252     return TRUE;
14253 }
14254
14255 void
14256 MailMoveEvent ()
14257 {
14258 #if !WIN32
14259     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14260     FILE *commandOutput;
14261     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14262     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14263     int nBuffers;
14264     int i;
14265     int archived;
14266     char *arcDir;
14267
14268     if (! cmailMsgLoaded) {
14269         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14270         return;
14271     }
14272
14273     if (nCmailGames == nCmailResults) {
14274         DisplayError(_("No unfinished games"), 0);
14275         return;
14276     }
14277
14278 #if CMAIL_PROHIBIT_REMAIL
14279     if (cmailMailedMove) {
14280       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);
14281         DisplayError(msg, 0);
14282         return;
14283     }
14284 #endif
14285
14286     if (! (cmailMailedMove || RegisterMove())) return;
14287
14288     if (   cmailMailedMove
14289         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14290       snprintf(string, MSG_SIZ, partCommandString,
14291                appData.debugMode ? " -v" : "", appData.cmailGameName);
14292         commandOutput = popen(string, "r");
14293
14294         if (commandOutput == NULL) {
14295             DisplayError(_("Failed to invoke cmail"), 0);
14296         } else {
14297             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14298                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14299             }
14300             if (nBuffers > 1) {
14301                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14302                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14303                 nBytes = MSG_SIZ - 1;
14304             } else {
14305                 (void) memcpy(msg, buffer, nBytes);
14306             }
14307             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14308
14309             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14310                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14311
14312                 archived = TRUE;
14313                 for (i = 0; i < nCmailGames; i ++) {
14314                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14315                         archived = FALSE;
14316                     }
14317                 }
14318                 if (   archived
14319                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14320                         != NULL)) {
14321                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14322                            arcDir,
14323                            appData.cmailGameName,
14324                            gameInfo.date);
14325                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14326                     cmailMsgLoaded = FALSE;
14327                 }
14328             }
14329
14330             DisplayInformation(msg);
14331             pclose(commandOutput);
14332         }
14333     } else {
14334         if ((*cmailMsg) != '\0') {
14335             DisplayInformation(cmailMsg);
14336         }
14337     }
14338
14339     return;
14340 #endif /* !WIN32 */
14341 }
14342
14343 char *
14344 CmailMsg ()
14345 {
14346 #if WIN32
14347     return NULL;
14348 #else
14349     int  prependComma = 0;
14350     char number[5];
14351     char string[MSG_SIZ];       /* Space for game-list */
14352     int  i;
14353
14354     if (!cmailMsgLoaded) return "";
14355
14356     if (cmailMailedMove) {
14357       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14358     } else {
14359         /* Create a list of games left */
14360       snprintf(string, MSG_SIZ, "[");
14361         for (i = 0; i < nCmailGames; i ++) {
14362             if (! (   cmailMoveRegistered[i]
14363                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14364                 if (prependComma) {
14365                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14366                 } else {
14367                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14368                     prependComma = 1;
14369                 }
14370
14371                 strcat(string, number);
14372             }
14373         }
14374         strcat(string, "]");
14375
14376         if (nCmailMovesRegistered + nCmailResults == 0) {
14377             switch (nCmailGames) {
14378               case 1:
14379                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14380                 break;
14381
14382               case 2:
14383                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14384                 break;
14385
14386               default:
14387                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14388                          nCmailGames);
14389                 break;
14390             }
14391         } else {
14392             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14393               case 1:
14394                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14395                          string);
14396                 break;
14397
14398               case 0:
14399                 if (nCmailResults == nCmailGames) {
14400                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14401                 } else {
14402                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14403                 }
14404                 break;
14405
14406               default:
14407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14408                          string);
14409             }
14410         }
14411     }
14412     return cmailMsg;
14413 #endif /* WIN32 */
14414 }
14415
14416 void
14417 ResetGameEvent ()
14418 {
14419     if (gameMode == Training)
14420       SetTrainingModeOff();
14421
14422     Reset(TRUE, TRUE);
14423     cmailMsgLoaded = FALSE;
14424     if (appData.icsActive) {
14425       SendToICS(ics_prefix);
14426       SendToICS("refresh\n");
14427     }
14428 }
14429
14430 void
14431 ExitEvent (int status)
14432 {
14433     exiting++;
14434     if (exiting > 2) {
14435       /* Give up on clean exit */
14436       exit(status);
14437     }
14438     if (exiting > 1) {
14439       /* Keep trying for clean exit */
14440       return;
14441     }
14442
14443     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14444     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14445
14446     if (telnetISR != NULL) {
14447       RemoveInputSource(telnetISR);
14448     }
14449     if (icsPR != NoProc) {
14450       DestroyChildProcess(icsPR, TRUE);
14451     }
14452
14453     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14454     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14455
14456     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14457     /* make sure this other one finishes before killing it!                  */
14458     if(endingGame) { int count = 0;
14459         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14460         while(endingGame && count++ < 10) DoSleep(1);
14461         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14462     }
14463
14464     /* Kill off chess programs */
14465     if (first.pr != NoProc) {
14466         ExitAnalyzeMode();
14467
14468         DoSleep( appData.delayBeforeQuit );
14469         SendToProgram("quit\n", &first);
14470         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14471     }
14472     if (second.pr != NoProc) {
14473         DoSleep( appData.delayBeforeQuit );
14474         SendToProgram("quit\n", &second);
14475         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14476     }
14477     if (first.isr != NULL) {
14478         RemoveInputSource(first.isr);
14479     }
14480     if (second.isr != NULL) {
14481         RemoveInputSource(second.isr);
14482     }
14483
14484     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14485     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14486
14487     ShutDownFrontEnd();
14488     exit(status);
14489 }
14490
14491 void
14492 PauseEngine (ChessProgramState *cps)
14493 {
14494     SendToProgram("pause\n", cps);
14495     cps->pause = 2;
14496 }
14497
14498 void
14499 UnPauseEngine (ChessProgramState *cps)
14500 {
14501     SendToProgram("resume\n", cps);
14502     cps->pause = 1;
14503 }
14504
14505 void
14506 PauseEvent ()
14507 {
14508     if (appData.debugMode)
14509         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14510     if (pausing) {
14511         pausing = FALSE;
14512         ModeHighlight();
14513         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14514             StartClocks();
14515             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14516                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14517                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14518             }
14519             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14520             HandleMachineMove(stashedInputMove, stalledEngine);
14521             stalledEngine = NULL;
14522             return;
14523         }
14524         if (gameMode == MachinePlaysWhite ||
14525             gameMode == TwoMachinesPlay   ||
14526             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14527             if(first.pause)  UnPauseEngine(&first);
14528             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14529             if(second.pause) UnPauseEngine(&second);
14530             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14531             StartClocks();
14532         } else {
14533             DisplayBothClocks();
14534         }
14535         if (gameMode == PlayFromGameFile) {
14536             if (appData.timeDelay >= 0)
14537                 AutoPlayGameLoop();
14538         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14539             Reset(FALSE, TRUE);
14540             SendToICS(ics_prefix);
14541             SendToICS("refresh\n");
14542         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14543             ForwardInner(forwardMostMove);
14544         }
14545         pauseExamInvalid = FALSE;
14546     } else {
14547         switch (gameMode) {
14548           default:
14549             return;
14550           case IcsExamining:
14551             pauseExamForwardMostMove = forwardMostMove;
14552             pauseExamInvalid = FALSE;
14553             /* fall through */
14554           case IcsObserving:
14555           case IcsPlayingWhite:
14556           case IcsPlayingBlack:
14557             pausing = TRUE;
14558             ModeHighlight();
14559             return;
14560           case PlayFromGameFile:
14561             (void) StopLoadGameTimer();
14562             pausing = TRUE;
14563             ModeHighlight();
14564             break;
14565           case BeginningOfGame:
14566             if (appData.icsActive) return;
14567             /* else fall through */
14568           case MachinePlaysWhite:
14569           case MachinePlaysBlack:
14570           case TwoMachinesPlay:
14571             if (forwardMostMove == 0)
14572               return;           /* don't pause if no one has moved */
14573             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14574                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14575                 if(onMove->pause) {           // thinking engine can be paused
14576                     PauseEngine(onMove);      // do it
14577                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14578                         PauseEngine(onMove->other);
14579                     else
14580                         SendToProgram("easy\n", onMove->other);
14581                     StopClocks();
14582                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14583             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14584                 if(first.pause) {
14585                     PauseEngine(&first);
14586                     StopClocks();
14587                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14588             } else { // human on move, pause pondering by either method
14589                 if(first.pause)
14590                     PauseEngine(&first);
14591                 else if(appData.ponderNextMove)
14592                     SendToProgram("easy\n", &first);
14593                 StopClocks();
14594             }
14595             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14596           case AnalyzeMode:
14597             pausing = TRUE;
14598             ModeHighlight();
14599             break;
14600         }
14601     }
14602 }
14603
14604 void
14605 EditCommentEvent ()
14606 {
14607     char title[MSG_SIZ];
14608
14609     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14610       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14611     } else {
14612       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14613                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14614                parseList[currentMove - 1]);
14615     }
14616
14617     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14618 }
14619
14620
14621 void
14622 EditTagsEvent ()
14623 {
14624     char *tags = PGNTags(&gameInfo);
14625     bookUp = FALSE;
14626     EditTagsPopUp(tags, NULL);
14627     free(tags);
14628 }
14629
14630 void
14631 ToggleSecond ()
14632 {
14633   if(second.analyzing) {
14634     SendToProgram("exit\n", &second);
14635     second.analyzing = FALSE;
14636   } else {
14637     if (second.pr == NoProc) StartChessProgram(&second);
14638     InitChessProgram(&second, FALSE);
14639     FeedMovesToProgram(&second, currentMove);
14640
14641     SendToProgram("analyze\n", &second);
14642     second.analyzing = TRUE;
14643   }
14644 }
14645
14646 /* Toggle ShowThinking */
14647 void
14648 ToggleShowThinking()
14649 {
14650   appData.showThinking = !appData.showThinking;
14651   ShowThinkingEvent();
14652 }
14653
14654 int
14655 AnalyzeModeEvent ()
14656 {
14657     char buf[MSG_SIZ];
14658
14659     if (!first.analysisSupport) {
14660       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14661       DisplayError(buf, 0);
14662       return 0;
14663     }
14664     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14665     if (appData.icsActive) {
14666         if (gameMode != IcsObserving) {
14667           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14668             DisplayError(buf, 0);
14669             /* secure check */
14670             if (appData.icsEngineAnalyze) {
14671                 if (appData.debugMode)
14672                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14673                 ExitAnalyzeMode();
14674                 ModeHighlight();
14675             }
14676             return 0;
14677         }
14678         /* if enable, user wants to disable icsEngineAnalyze */
14679         if (appData.icsEngineAnalyze) {
14680                 ExitAnalyzeMode();
14681                 ModeHighlight();
14682                 return 0;
14683         }
14684         appData.icsEngineAnalyze = TRUE;
14685         if (appData.debugMode)
14686             fprintf(debugFP, "ICS engine analyze starting... \n");
14687     }
14688
14689     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14690     if (appData.noChessProgram || gameMode == AnalyzeMode)
14691       return 0;
14692
14693     if (gameMode != AnalyzeFile) {
14694         if (!appData.icsEngineAnalyze) {
14695                EditGameEvent();
14696                if (gameMode != EditGame) return 0;
14697         }
14698         if (!appData.showThinking) ToggleShowThinking();
14699         ResurrectChessProgram();
14700         SendToProgram("analyze\n", &first);
14701         first.analyzing = TRUE;
14702         /*first.maybeThinking = TRUE;*/
14703         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14704         EngineOutputPopUp();
14705     }
14706     if (!appData.icsEngineAnalyze) {
14707         gameMode = AnalyzeMode;
14708         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14709     }
14710     pausing = FALSE;
14711     ModeHighlight();
14712     SetGameInfo();
14713
14714     StartAnalysisClock();
14715     GetTimeMark(&lastNodeCountTime);
14716     lastNodeCount = 0;
14717     return 1;
14718 }
14719
14720 void
14721 AnalyzeFileEvent ()
14722 {
14723     if (appData.noChessProgram || gameMode == AnalyzeFile)
14724       return;
14725
14726     if (!first.analysisSupport) {
14727       char buf[MSG_SIZ];
14728       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14729       DisplayError(buf, 0);
14730       return;
14731     }
14732
14733     if (gameMode != AnalyzeMode) {
14734         keepInfo = 1; // mere annotating should not alter PGN tags
14735         EditGameEvent();
14736         keepInfo = 0;
14737         if (gameMode != EditGame) return;
14738         if (!appData.showThinking) ToggleShowThinking();
14739         ResurrectChessProgram();
14740         SendToProgram("analyze\n", &first);
14741         first.analyzing = TRUE;
14742         /*first.maybeThinking = TRUE;*/
14743         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14744         EngineOutputPopUp();
14745     }
14746     gameMode = AnalyzeFile;
14747     pausing = FALSE;
14748     ModeHighlight();
14749
14750     StartAnalysisClock();
14751     GetTimeMark(&lastNodeCountTime);
14752     lastNodeCount = 0;
14753     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14754     AnalysisPeriodicEvent(1);
14755 }
14756
14757 void
14758 MachineWhiteEvent ()
14759 {
14760     char buf[MSG_SIZ];
14761     char *bookHit = NULL;
14762
14763     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14764       return;
14765
14766
14767     if (gameMode == PlayFromGameFile ||
14768         gameMode == TwoMachinesPlay  ||
14769         gameMode == Training         ||
14770         gameMode == AnalyzeMode      ||
14771         gameMode == EndOfGame)
14772         EditGameEvent();
14773
14774     if (gameMode == EditPosition)
14775         EditPositionDone(TRUE);
14776
14777     if (!WhiteOnMove(currentMove)) {
14778         DisplayError(_("It is not White's turn"), 0);
14779         return;
14780     }
14781
14782     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14783       ExitAnalyzeMode();
14784
14785     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14786         gameMode == AnalyzeFile)
14787         TruncateGame();
14788
14789     ResurrectChessProgram();    /* in case it isn't running */
14790     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14791         gameMode = MachinePlaysWhite;
14792         ResetClocks();
14793     } else
14794     gameMode = MachinePlaysWhite;
14795     pausing = FALSE;
14796     ModeHighlight();
14797     SetGameInfo();
14798     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14799     DisplayTitle(buf);
14800     if (first.sendName) {
14801       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14802       SendToProgram(buf, &first);
14803     }
14804     if (first.sendTime) {
14805       if (first.useColors) {
14806         SendToProgram("black\n", &first); /*gnu kludge*/
14807       }
14808       SendTimeRemaining(&first, TRUE);
14809     }
14810     if (first.useColors) {
14811       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14812     }
14813     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14814     SetMachineThinkingEnables();
14815     first.maybeThinking = TRUE;
14816     StartClocks();
14817     firstMove = FALSE;
14818
14819     if (appData.autoFlipView && !flipView) {
14820       flipView = !flipView;
14821       DrawPosition(FALSE, NULL);
14822       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14823     }
14824
14825     if(bookHit) { // [HGM] book: simulate book reply
14826         static char bookMove[MSG_SIZ]; // a bit generous?
14827
14828         programStats.nodes = programStats.depth = programStats.time =
14829         programStats.score = programStats.got_only_move = 0;
14830         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14831
14832         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14833         strcat(bookMove, bookHit);
14834         HandleMachineMove(bookMove, &first);
14835     }
14836 }
14837
14838 void
14839 MachineBlackEvent ()
14840 {
14841   char buf[MSG_SIZ];
14842   char *bookHit = NULL;
14843
14844     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14845         return;
14846
14847
14848     if (gameMode == PlayFromGameFile ||
14849         gameMode == TwoMachinesPlay  ||
14850         gameMode == Training         ||
14851         gameMode == AnalyzeMode      ||
14852         gameMode == EndOfGame)
14853         EditGameEvent();
14854
14855     if (gameMode == EditPosition)
14856         EditPositionDone(TRUE);
14857
14858     if (WhiteOnMove(currentMove)) {
14859         DisplayError(_("It is not Black's turn"), 0);
14860         return;
14861     }
14862
14863     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14864       ExitAnalyzeMode();
14865
14866     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14867         gameMode == AnalyzeFile)
14868         TruncateGame();
14869
14870     ResurrectChessProgram();    /* in case it isn't running */
14871     gameMode = MachinePlaysBlack;
14872     pausing = FALSE;
14873     ModeHighlight();
14874     SetGameInfo();
14875     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14876     DisplayTitle(buf);
14877     if (first.sendName) {
14878       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14879       SendToProgram(buf, &first);
14880     }
14881     if (first.sendTime) {
14882       if (first.useColors) {
14883         SendToProgram("white\n", &first); /*gnu kludge*/
14884       }
14885       SendTimeRemaining(&first, FALSE);
14886     }
14887     if (first.useColors) {
14888       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14889     }
14890     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14891     SetMachineThinkingEnables();
14892     first.maybeThinking = TRUE;
14893     StartClocks();
14894
14895     if (appData.autoFlipView && flipView) {
14896       flipView = !flipView;
14897       DrawPosition(FALSE, NULL);
14898       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14899     }
14900     if(bookHit) { // [HGM] book: simulate book reply
14901         static char bookMove[MSG_SIZ]; // a bit generous?
14902
14903         programStats.nodes = programStats.depth = programStats.time =
14904         programStats.score = programStats.got_only_move = 0;
14905         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14906
14907         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14908         strcat(bookMove, bookHit);
14909         HandleMachineMove(bookMove, &first);
14910     }
14911 }
14912
14913
14914 void
14915 DisplayTwoMachinesTitle ()
14916 {
14917     char buf[MSG_SIZ];
14918     if (appData.matchGames > 0) {
14919         if(appData.tourneyFile[0]) {
14920           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14921                    gameInfo.white, _("vs."), gameInfo.black,
14922                    nextGame+1, appData.matchGames+1,
14923                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14924         } else
14925         if (first.twoMachinesColor[0] == 'w') {
14926           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14927                    gameInfo.white, _("vs."),  gameInfo.black,
14928                    first.matchWins, second.matchWins,
14929                    matchGame - 1 - (first.matchWins + second.matchWins));
14930         } else {
14931           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14932                    gameInfo.white, _("vs."), gameInfo.black,
14933                    second.matchWins, first.matchWins,
14934                    matchGame - 1 - (first.matchWins + second.matchWins));
14935         }
14936     } else {
14937       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14938     }
14939     DisplayTitle(buf);
14940 }
14941
14942 void
14943 SettingsMenuIfReady ()
14944 {
14945   if (second.lastPing != second.lastPong) {
14946     DisplayMessage("", _("Waiting for second chess program"));
14947     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14948     return;
14949   }
14950   ThawUI();
14951   DisplayMessage("", "");
14952   SettingsPopUp(&second);
14953 }
14954
14955 int
14956 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14957 {
14958     char buf[MSG_SIZ];
14959     if (cps->pr == NoProc) {
14960         StartChessProgram(cps);
14961         if (cps->protocolVersion == 1) {
14962           retry();
14963           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14964         } else {
14965           /* kludge: allow timeout for initial "feature" command */
14966           if(retry != TwoMachinesEventIfReady) FreezeUI();
14967           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14968           DisplayMessage("", buf);
14969           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14970         }
14971         return 1;
14972     }
14973     return 0;
14974 }
14975
14976 void
14977 TwoMachinesEvent P((void))
14978 {
14979     int i;
14980     char buf[MSG_SIZ];
14981     ChessProgramState *onmove;
14982     char *bookHit = NULL;
14983     static int stalling = 0;
14984     TimeMark now;
14985     long wait;
14986
14987     if (appData.noChessProgram) return;
14988
14989     switch (gameMode) {
14990       case TwoMachinesPlay:
14991         return;
14992       case MachinePlaysWhite:
14993       case MachinePlaysBlack:
14994         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14995             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14996             return;
14997         }
14998         /* fall through */
14999       case BeginningOfGame:
15000       case PlayFromGameFile:
15001       case EndOfGame:
15002         EditGameEvent();
15003         if (gameMode != EditGame) return;
15004         break;
15005       case EditPosition:
15006         EditPositionDone(TRUE);
15007         break;
15008       case AnalyzeMode:
15009       case AnalyzeFile:
15010         ExitAnalyzeMode();
15011         break;
15012       case EditGame:
15013       default:
15014         break;
15015     }
15016
15017 //    forwardMostMove = currentMove;
15018     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15019     startingEngine = TRUE;
15020
15021     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15022
15023     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15024     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15025       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15026       return;
15027     }
15028   if(!appData.epd) {
15029     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15030
15031     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15032                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15033         startingEngine = matchMode = FALSE;
15034         DisplayError("second engine does not play this", 0);
15035         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15036         EditGameEvent(); // switch back to EditGame mode
15037         return;
15038     }
15039
15040     if(!stalling) {
15041       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15042       SendToProgram("force\n", &second);
15043       stalling = 1;
15044       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15045       return;
15046     }
15047   }
15048     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15049     if(appData.matchPause>10000 || appData.matchPause<10)
15050                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15051     wait = SubtractTimeMarks(&now, &pauseStart);
15052     if(wait < appData.matchPause) {
15053         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15054         return;
15055     }
15056     // we are now committed to starting the game
15057     stalling = 0;
15058     DisplayMessage("", "");
15059   if(!appData.epd) {
15060     if (startedFromSetupPosition) {
15061         SendBoard(&second, backwardMostMove);
15062     if (appData.debugMode) {
15063         fprintf(debugFP, "Two Machines\n");
15064     }
15065     }
15066     for (i = backwardMostMove; i < forwardMostMove; i++) {
15067         SendMoveToProgram(i, &second);
15068     }
15069   }
15070
15071     gameMode = TwoMachinesPlay;
15072     pausing = startingEngine = FALSE;
15073     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15074     SetGameInfo();
15075     DisplayTwoMachinesTitle();
15076     firstMove = TRUE;
15077     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15078         onmove = &first;
15079     } else {
15080         onmove = &second;
15081     }
15082     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15083     SendToProgram(first.computerString, &first);
15084     if (first.sendName) {
15085       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15086       SendToProgram(buf, &first);
15087     }
15088   if(!appData.epd) {
15089     SendToProgram(second.computerString, &second);
15090     if (second.sendName) {
15091       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15092       SendToProgram(buf, &second);
15093     }
15094   }
15095
15096     ResetClocks();
15097     if (!first.sendTime || !second.sendTime) {
15098         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15099         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15100     }
15101     if (onmove->sendTime) {
15102       if (onmove->useColors) {
15103         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15104       }
15105       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15106     }
15107     if (onmove->useColors) {
15108       SendToProgram(onmove->twoMachinesColor, onmove);
15109     }
15110     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15111 //    SendToProgram("go\n", onmove);
15112     onmove->maybeThinking = TRUE;
15113     SetMachineThinkingEnables();
15114
15115     StartClocks();
15116
15117     if(bookHit) { // [HGM] book: simulate book reply
15118         static char bookMove[MSG_SIZ]; // a bit generous?
15119
15120         programStats.nodes = programStats.depth = programStats.time =
15121         programStats.score = programStats.got_only_move = 0;
15122         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15123
15124         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15125         strcat(bookMove, bookHit);
15126         savedMessage = bookMove; // args for deferred call
15127         savedState = onmove;
15128         ScheduleDelayedEvent(DeferredBookMove, 1);
15129     }
15130 }
15131
15132 void
15133 TrainingEvent ()
15134 {
15135     if (gameMode == Training) {
15136       SetTrainingModeOff();
15137       gameMode = PlayFromGameFile;
15138       DisplayMessage("", _("Training mode off"));
15139     } else {
15140       gameMode = Training;
15141       animateTraining = appData.animate;
15142
15143       /* make sure we are not already at the end of the game */
15144       if (currentMove < forwardMostMove) {
15145         SetTrainingModeOn();
15146         DisplayMessage("", _("Training mode on"));
15147       } else {
15148         gameMode = PlayFromGameFile;
15149         DisplayError(_("Already at end of game"), 0);
15150       }
15151     }
15152     ModeHighlight();
15153 }
15154
15155 void
15156 IcsClientEvent ()
15157 {
15158     if (!appData.icsActive) return;
15159     switch (gameMode) {
15160       case IcsPlayingWhite:
15161       case IcsPlayingBlack:
15162       case IcsObserving:
15163       case IcsIdle:
15164       case BeginningOfGame:
15165       case IcsExamining:
15166         return;
15167
15168       case EditGame:
15169         break;
15170
15171       case EditPosition:
15172         EditPositionDone(TRUE);
15173         break;
15174
15175       case AnalyzeMode:
15176       case AnalyzeFile:
15177         ExitAnalyzeMode();
15178         break;
15179
15180       default:
15181         EditGameEvent();
15182         break;
15183     }
15184
15185     gameMode = IcsIdle;
15186     ModeHighlight();
15187     return;
15188 }
15189
15190 void
15191 EditGameEvent ()
15192 {
15193     int i;
15194
15195     switch (gameMode) {
15196       case Training:
15197         SetTrainingModeOff();
15198         break;
15199       case MachinePlaysWhite:
15200       case MachinePlaysBlack:
15201       case BeginningOfGame:
15202         SendToProgram("force\n", &first);
15203         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15204             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15205                 char buf[MSG_SIZ];
15206                 abortEngineThink = TRUE;
15207                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15208                 SendToProgram(buf, &first);
15209                 DisplayMessage("Aborting engine think", "");
15210                 FreezeUI();
15211             }
15212         }
15213         SetUserThinkingEnables();
15214         break;
15215       case PlayFromGameFile:
15216         (void) StopLoadGameTimer();
15217         if (gameFileFP != NULL) {
15218             gameFileFP = NULL;
15219         }
15220         break;
15221       case EditPosition:
15222         EditPositionDone(TRUE);
15223         break;
15224       case AnalyzeMode:
15225       case AnalyzeFile:
15226         ExitAnalyzeMode();
15227         SendToProgram("force\n", &first);
15228         break;
15229       case TwoMachinesPlay:
15230         GameEnds(EndOfFile, NULL, GE_PLAYER);
15231         ResurrectChessProgram();
15232         SetUserThinkingEnables();
15233         break;
15234       case EndOfGame:
15235         ResurrectChessProgram();
15236         break;
15237       case IcsPlayingBlack:
15238       case IcsPlayingWhite:
15239         DisplayError(_("Warning: You are still playing a game"), 0);
15240         break;
15241       case IcsObserving:
15242         DisplayError(_("Warning: You are still observing a game"), 0);
15243         break;
15244       case IcsExamining:
15245         DisplayError(_("Warning: You are still examining a game"), 0);
15246         break;
15247       case IcsIdle:
15248         break;
15249       case EditGame:
15250       default:
15251         return;
15252     }
15253
15254     pausing = FALSE;
15255     StopClocks();
15256     first.offeredDraw = second.offeredDraw = 0;
15257
15258     if (gameMode == PlayFromGameFile) {
15259         whiteTimeRemaining = timeRemaining[0][currentMove];
15260         blackTimeRemaining = timeRemaining[1][currentMove];
15261         DisplayTitle("");
15262     }
15263
15264     if (gameMode == MachinePlaysWhite ||
15265         gameMode == MachinePlaysBlack ||
15266         gameMode == TwoMachinesPlay ||
15267         gameMode == EndOfGame) {
15268         i = forwardMostMove;
15269         while (i > currentMove) {
15270             SendToProgram("undo\n", &first);
15271             i--;
15272         }
15273         if(!adjustedClock) {
15274         whiteTimeRemaining = timeRemaining[0][currentMove];
15275         blackTimeRemaining = timeRemaining[1][currentMove];
15276         DisplayBothClocks();
15277         }
15278         if (whiteFlag || blackFlag) {
15279             whiteFlag = blackFlag = 0;
15280         }
15281         DisplayTitle("");
15282     }
15283
15284     gameMode = EditGame;
15285     ModeHighlight();
15286     SetGameInfo();
15287 }
15288
15289
15290 void
15291 EditPositionEvent ()
15292 {
15293     if (gameMode == EditPosition) {
15294         EditGameEvent();
15295         return;
15296     }
15297
15298     EditGameEvent();
15299     if (gameMode != EditGame) return;
15300
15301     gameMode = EditPosition;
15302     ModeHighlight();
15303     SetGameInfo();
15304     if (currentMove > 0)
15305       CopyBoard(boards[0], boards[currentMove]);
15306
15307     blackPlaysFirst = !WhiteOnMove(currentMove);
15308     ResetClocks();
15309     currentMove = forwardMostMove = backwardMostMove = 0;
15310     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15311     DisplayMove(-1);
15312     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15313 }
15314
15315 void
15316 ExitAnalyzeMode ()
15317 {
15318     /* [DM] icsEngineAnalyze - possible call from other functions */
15319     if (appData.icsEngineAnalyze) {
15320         appData.icsEngineAnalyze = FALSE;
15321
15322         DisplayMessage("",_("Close ICS engine analyze..."));
15323     }
15324     if (first.analysisSupport && first.analyzing) {
15325       SendToBoth("exit\n");
15326       first.analyzing = second.analyzing = FALSE;
15327     }
15328     thinkOutput[0] = NULLCHAR;
15329 }
15330
15331 void
15332 EditPositionDone (Boolean fakeRights)
15333 {
15334     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15335
15336     startedFromSetupPosition = TRUE;
15337     InitChessProgram(&first, FALSE);
15338     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15339       boards[0][EP_STATUS] = EP_NONE;
15340       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15341       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15342         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15343         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15344       } else boards[0][CASTLING][2] = NoRights;
15345       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15346         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15347         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15348       } else boards[0][CASTLING][5] = NoRights;
15349       if(gameInfo.variant == VariantSChess) {
15350         int i;
15351         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15352           boards[0][VIRGIN][i] = 0;
15353           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15354           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15355         }
15356       }
15357     }
15358     SendToProgram("force\n", &first);
15359     if (blackPlaysFirst) {
15360         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15361         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15362         currentMove = forwardMostMove = backwardMostMove = 1;
15363         CopyBoard(boards[1], boards[0]);
15364     } else {
15365         currentMove = forwardMostMove = backwardMostMove = 0;
15366     }
15367     SendBoard(&first, forwardMostMove);
15368     if (appData.debugMode) {
15369         fprintf(debugFP, "EditPosDone\n");
15370     }
15371     DisplayTitle("");
15372     DisplayMessage("", "");
15373     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15374     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15375     gameMode = EditGame;
15376     ModeHighlight();
15377     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15378     ClearHighlights(); /* [AS] */
15379 }
15380
15381 /* Pause for `ms' milliseconds */
15382 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15383 void
15384 TimeDelay (long ms)
15385 {
15386     TimeMark m1, m2;
15387
15388     GetTimeMark(&m1);
15389     do {
15390         GetTimeMark(&m2);
15391     } while (SubtractTimeMarks(&m2, &m1) < ms);
15392 }
15393
15394 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15395 void
15396 SendMultiLineToICS (char *buf)
15397 {
15398     char temp[MSG_SIZ+1], *p;
15399     int len;
15400
15401     len = strlen(buf);
15402     if (len > MSG_SIZ)
15403       len = MSG_SIZ;
15404
15405     strncpy(temp, buf, len);
15406     temp[len] = 0;
15407
15408     p = temp;
15409     while (*p) {
15410         if (*p == '\n' || *p == '\r')
15411           *p = ' ';
15412         ++p;
15413     }
15414
15415     strcat(temp, "\n");
15416     SendToICS(temp);
15417     SendToPlayer(temp, strlen(temp));
15418 }
15419
15420 void
15421 SetWhiteToPlayEvent ()
15422 {
15423     if (gameMode == EditPosition) {
15424         blackPlaysFirst = FALSE;
15425         DisplayBothClocks();    /* works because currentMove is 0 */
15426     } else if (gameMode == IcsExamining) {
15427         SendToICS(ics_prefix);
15428         SendToICS("tomove white\n");
15429     }
15430 }
15431
15432 void
15433 SetBlackToPlayEvent ()
15434 {
15435     if (gameMode == EditPosition) {
15436         blackPlaysFirst = TRUE;
15437         currentMove = 1;        /* kludge */
15438         DisplayBothClocks();
15439         currentMove = 0;
15440     } else if (gameMode == IcsExamining) {
15441         SendToICS(ics_prefix);
15442         SendToICS("tomove black\n");
15443     }
15444 }
15445
15446 void
15447 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15448 {
15449     char buf[MSG_SIZ];
15450     ChessSquare piece = boards[0][y][x];
15451     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15452     static int lastVariant;
15453
15454     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15455
15456     switch (selection) {
15457       case ClearBoard:
15458         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15459         MarkTargetSquares(1);
15460         CopyBoard(currentBoard, boards[0]);
15461         CopyBoard(menuBoard, initialPosition);
15462         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15463             SendToICS(ics_prefix);
15464             SendToICS("bsetup clear\n");
15465         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15466             SendToICS(ics_prefix);
15467             SendToICS("clearboard\n");
15468         } else {
15469             int nonEmpty = 0;
15470             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15471                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15472                 for (y = 0; y < BOARD_HEIGHT; y++) {
15473                     if (gameMode == IcsExamining) {
15474                         if (boards[currentMove][y][x] != EmptySquare) {
15475                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15476                                     AAA + x, ONE + y);
15477                             SendToICS(buf);
15478                         }
15479                     } else if(boards[0][y][x] != DarkSquare) {
15480                         if(boards[0][y][x] != p) nonEmpty++;
15481                         boards[0][y][x] = p;
15482                     }
15483                 }
15484             }
15485             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15486                 int r;
15487                 for(r = 0; r < BOARD_HEIGHT; r++) {
15488                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15489                     ChessSquare p = menuBoard[r][x];
15490                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15491                   }
15492                 }
15493                 DisplayMessage("Clicking clock again restores position", "");
15494                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15495                 if(!nonEmpty) { // asked to clear an empty board
15496                     CopyBoard(boards[0], menuBoard);
15497                 } else
15498                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15499                     CopyBoard(boards[0], initialPosition);
15500                 } else
15501                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15502                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15503                     CopyBoard(boards[0], erasedBoard);
15504                 } else
15505                     CopyBoard(erasedBoard, currentBoard);
15506
15507             }
15508         }
15509         if (gameMode == EditPosition) {
15510             DrawPosition(FALSE, boards[0]);
15511         }
15512         break;
15513
15514       case WhitePlay:
15515         SetWhiteToPlayEvent();
15516         break;
15517
15518       case BlackPlay:
15519         SetBlackToPlayEvent();
15520         break;
15521
15522       case EmptySquare:
15523         if (gameMode == IcsExamining) {
15524             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15525             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15526             SendToICS(buf);
15527         } else {
15528             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15529                 if(x == BOARD_LEFT-2) {
15530                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15531                     boards[0][y][1] = 0;
15532                 } else
15533                 if(x == BOARD_RGHT+1) {
15534                     if(y >= gameInfo.holdingsSize) break;
15535                     boards[0][y][BOARD_WIDTH-2] = 0;
15536                 } else break;
15537             }
15538             boards[0][y][x] = EmptySquare;
15539             DrawPosition(FALSE, boards[0]);
15540         }
15541         break;
15542
15543       case PromotePiece:
15544         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15545            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15546             selection = (ChessSquare) (PROMOTED(piece));
15547         } else if(piece == EmptySquare) selection = WhiteSilver;
15548         else selection = (ChessSquare)((int)piece - 1);
15549         goto defaultlabel;
15550
15551       case DemotePiece:
15552         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15553            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15554             selection = (ChessSquare) (DEMOTED(piece));
15555         } else if(piece == EmptySquare) selection = BlackSilver;
15556         else selection = (ChessSquare)((int)piece + 1);
15557         goto defaultlabel;
15558
15559       case WhiteQueen:
15560       case BlackQueen:
15561         if(gameInfo.variant == VariantShatranj ||
15562            gameInfo.variant == VariantXiangqi  ||
15563            gameInfo.variant == VariantCourier  ||
15564            gameInfo.variant == VariantASEAN    ||
15565            gameInfo.variant == VariantMakruk     )
15566             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15567         goto defaultlabel;
15568
15569       case WhiteKing:
15570       case BlackKing:
15571         if(gameInfo.variant == VariantXiangqi)
15572             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15573         if(gameInfo.variant == VariantKnightmate)
15574             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15575       default:
15576         defaultlabel:
15577         if (gameMode == IcsExamining) {
15578             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15579             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15580                      PieceToChar(selection), AAA + x, ONE + y);
15581             SendToICS(buf);
15582         } else {
15583             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15584                 int n;
15585                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15586                     n = PieceToNumber(selection - BlackPawn);
15587                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15588                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15589                     boards[0][BOARD_HEIGHT-1-n][1]++;
15590                 } else
15591                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15592                     n = PieceToNumber(selection);
15593                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15594                     boards[0][n][BOARD_WIDTH-1] = selection;
15595                     boards[0][n][BOARD_WIDTH-2]++;
15596                 }
15597             } else
15598             boards[0][y][x] = selection;
15599             DrawPosition(TRUE, boards[0]);
15600             ClearHighlights();
15601             fromX = fromY = -1;
15602         }
15603         break;
15604     }
15605 }
15606
15607
15608 void
15609 DropMenuEvent (ChessSquare selection, int x, int y)
15610 {
15611     ChessMove moveType;
15612
15613     switch (gameMode) {
15614       case IcsPlayingWhite:
15615       case MachinePlaysBlack:
15616         if (!WhiteOnMove(currentMove)) {
15617             DisplayMoveError(_("It is Black's turn"));
15618             return;
15619         }
15620         moveType = WhiteDrop;
15621         break;
15622       case IcsPlayingBlack:
15623       case MachinePlaysWhite:
15624         if (WhiteOnMove(currentMove)) {
15625             DisplayMoveError(_("It is White's turn"));
15626             return;
15627         }
15628         moveType = BlackDrop;
15629         break;
15630       case EditGame:
15631         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15632         break;
15633       default:
15634         return;
15635     }
15636
15637     if (moveType == BlackDrop && selection < BlackPawn) {
15638       selection = (ChessSquare) ((int) selection
15639                                  + (int) BlackPawn - (int) WhitePawn);
15640     }
15641     if (boards[currentMove][y][x] != EmptySquare) {
15642         DisplayMoveError(_("That square is occupied"));
15643         return;
15644     }
15645
15646     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15647 }
15648
15649 void
15650 AcceptEvent ()
15651 {
15652     /* Accept a pending offer of any kind from opponent */
15653
15654     if (appData.icsActive) {
15655         SendToICS(ics_prefix);
15656         SendToICS("accept\n");
15657     } else if (cmailMsgLoaded) {
15658         if (currentMove == cmailOldMove &&
15659             commentList[cmailOldMove] != NULL &&
15660             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15661                    "Black offers a draw" : "White offers a draw")) {
15662             TruncateGame();
15663             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15664             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15665         } else {
15666             DisplayError(_("There is no pending offer on this move"), 0);
15667             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15668         }
15669     } else {
15670         /* Not used for offers from chess program */
15671     }
15672 }
15673
15674 void
15675 DeclineEvent ()
15676 {
15677     /* Decline a pending offer of any kind from opponent */
15678
15679     if (appData.icsActive) {
15680         SendToICS(ics_prefix);
15681         SendToICS("decline\n");
15682     } else if (cmailMsgLoaded) {
15683         if (currentMove == cmailOldMove &&
15684             commentList[cmailOldMove] != NULL &&
15685             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15686                    "Black offers a draw" : "White offers a draw")) {
15687 #ifdef NOTDEF
15688             AppendComment(cmailOldMove, "Draw declined", TRUE);
15689             DisplayComment(cmailOldMove - 1, "Draw declined");
15690 #endif /*NOTDEF*/
15691         } else {
15692             DisplayError(_("There is no pending offer on this move"), 0);
15693         }
15694     } else {
15695         /* Not used for offers from chess program */
15696     }
15697 }
15698
15699 void
15700 RematchEvent ()
15701 {
15702     /* Issue ICS rematch command */
15703     if (appData.icsActive) {
15704         SendToICS(ics_prefix);
15705         SendToICS("rematch\n");
15706     }
15707 }
15708
15709 void
15710 CallFlagEvent ()
15711 {
15712     /* Call your opponent's flag (claim a win on time) */
15713     if (appData.icsActive) {
15714         SendToICS(ics_prefix);
15715         SendToICS("flag\n");
15716     } else {
15717         switch (gameMode) {
15718           default:
15719             return;
15720           case MachinePlaysWhite:
15721             if (whiteFlag) {
15722                 if (blackFlag)
15723                   GameEnds(GameIsDrawn, "Both players ran out of time",
15724                            GE_PLAYER);
15725                 else
15726                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15727             } else {
15728                 DisplayError(_("Your opponent is not out of time"), 0);
15729             }
15730             break;
15731           case MachinePlaysBlack:
15732             if (blackFlag) {
15733                 if (whiteFlag)
15734                   GameEnds(GameIsDrawn, "Both players ran out of time",
15735                            GE_PLAYER);
15736                 else
15737                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15738             } else {
15739                 DisplayError(_("Your opponent is not out of time"), 0);
15740             }
15741             break;
15742         }
15743     }
15744 }
15745
15746 void
15747 ClockClick (int which)
15748 {       // [HGM] code moved to back-end from winboard.c
15749         if(which) { // black clock
15750           if (gameMode == EditPosition || gameMode == IcsExamining) {
15751             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15752             SetBlackToPlayEvent();
15753           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15754                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15755           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15756           } else if (shiftKey) {
15757             AdjustClock(which, -1);
15758           } else if (gameMode == IcsPlayingWhite ||
15759                      gameMode == MachinePlaysBlack) {
15760             CallFlagEvent();
15761           }
15762         } else { // white clock
15763           if (gameMode == EditPosition || gameMode == IcsExamining) {
15764             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15765             SetWhiteToPlayEvent();
15766           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15767                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15768           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15769           } else if (shiftKey) {
15770             AdjustClock(which, -1);
15771           } else if (gameMode == IcsPlayingBlack ||
15772                    gameMode == MachinePlaysWhite) {
15773             CallFlagEvent();
15774           }
15775         }
15776 }
15777
15778 void
15779 DrawEvent ()
15780 {
15781     /* Offer draw or accept pending draw offer from opponent */
15782
15783     if (appData.icsActive) {
15784         /* Note: tournament rules require draw offers to be
15785            made after you make your move but before you punch
15786            your clock.  Currently ICS doesn't let you do that;
15787            instead, you immediately punch your clock after making
15788            a move, but you can offer a draw at any time. */
15789
15790         SendToICS(ics_prefix);
15791         SendToICS("draw\n");
15792         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15793     } else if (cmailMsgLoaded) {
15794         if (currentMove == cmailOldMove &&
15795             commentList[cmailOldMove] != NULL &&
15796             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15797                    "Black offers a draw" : "White offers a draw")) {
15798             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15799             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15800         } else if (currentMove == cmailOldMove + 1) {
15801             char *offer = WhiteOnMove(cmailOldMove) ?
15802               "White offers a draw" : "Black offers a draw";
15803             AppendComment(currentMove, offer, TRUE);
15804             DisplayComment(currentMove - 1, offer);
15805             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15806         } else {
15807             DisplayError(_("You must make your move before offering a draw"), 0);
15808             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15809         }
15810     } else if (first.offeredDraw) {
15811         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15812     } else {
15813         if (first.sendDrawOffers) {
15814             SendToProgram("draw\n", &first);
15815             userOfferedDraw = TRUE;
15816         }
15817     }
15818 }
15819
15820 void
15821 AdjournEvent ()
15822 {
15823     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15824
15825     if (appData.icsActive) {
15826         SendToICS(ics_prefix);
15827         SendToICS("adjourn\n");
15828     } else {
15829         /* Currently GNU Chess doesn't offer or accept Adjourns */
15830     }
15831 }
15832
15833
15834 void
15835 AbortEvent ()
15836 {
15837     /* Offer Abort or accept pending Abort offer from opponent */
15838
15839     if (appData.icsActive) {
15840         SendToICS(ics_prefix);
15841         SendToICS("abort\n");
15842     } else {
15843         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15844     }
15845 }
15846
15847 void
15848 ResignEvent ()
15849 {
15850     /* Resign.  You can do this even if it's not your turn. */
15851
15852     if (appData.icsActive) {
15853         SendToICS(ics_prefix);
15854         SendToICS("resign\n");
15855     } else {
15856         switch (gameMode) {
15857           case MachinePlaysWhite:
15858             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15859             break;
15860           case MachinePlaysBlack:
15861             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15862             break;
15863           case EditGame:
15864             if (cmailMsgLoaded) {
15865                 TruncateGame();
15866                 if (WhiteOnMove(cmailOldMove)) {
15867                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15868                 } else {
15869                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15870                 }
15871                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15872             }
15873             break;
15874           default:
15875             break;
15876         }
15877     }
15878 }
15879
15880
15881 void
15882 StopObservingEvent ()
15883 {
15884     /* Stop observing current games */
15885     SendToICS(ics_prefix);
15886     SendToICS("unobserve\n");
15887 }
15888
15889 void
15890 StopExaminingEvent ()
15891 {
15892     /* Stop observing current game */
15893     SendToICS(ics_prefix);
15894     SendToICS("unexamine\n");
15895 }
15896
15897 void
15898 ForwardInner (int target)
15899 {
15900     int limit; int oldSeekGraphUp = seekGraphUp;
15901
15902     if (appData.debugMode)
15903         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15904                 target, currentMove, forwardMostMove);
15905
15906     if (gameMode == EditPosition)
15907       return;
15908
15909     seekGraphUp = FALSE;
15910     MarkTargetSquares(1);
15911     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15912
15913     if (gameMode == PlayFromGameFile && !pausing)
15914       PauseEvent();
15915
15916     if (gameMode == IcsExamining && pausing)
15917       limit = pauseExamForwardMostMove;
15918     else
15919       limit = forwardMostMove;
15920
15921     if (target > limit) target = limit;
15922
15923     if (target > 0 && moveList[target - 1][0]) {
15924         int fromX, fromY, toX, toY;
15925         toX = moveList[target - 1][2] - AAA;
15926         toY = moveList[target - 1][3] - ONE;
15927         if (moveList[target - 1][1] == '@') {
15928             if (appData.highlightLastMove) {
15929                 SetHighlights(-1, -1, toX, toY);
15930             }
15931         } else {
15932             fromX = moveList[target - 1][0] - AAA;
15933             fromY = moveList[target - 1][1] - ONE;
15934             if (target == currentMove + 1) {
15935                 if(moveList[target - 1][4] == ';') { // multi-leg
15936                     killX = moveList[target - 1][5] - AAA;
15937                     killY = moveList[target - 1][6] - ONE;
15938                 }
15939                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15940                 killX = killY = -1;
15941             }
15942             if (appData.highlightLastMove) {
15943                 SetHighlights(fromX, fromY, toX, toY);
15944             }
15945         }
15946     }
15947     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15948         gameMode == Training || gameMode == PlayFromGameFile ||
15949         gameMode == AnalyzeFile) {
15950         while (currentMove < target) {
15951             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15952             SendMoveToProgram(currentMove++, &first);
15953         }
15954     } else {
15955         currentMove = target;
15956     }
15957
15958     if (gameMode == EditGame || gameMode == EndOfGame) {
15959         whiteTimeRemaining = timeRemaining[0][currentMove];
15960         blackTimeRemaining = timeRemaining[1][currentMove];
15961     }
15962     DisplayBothClocks();
15963     DisplayMove(currentMove - 1);
15964     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15965     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15966     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15967         DisplayComment(currentMove - 1, commentList[currentMove]);
15968     }
15969     ClearMap(); // [HGM] exclude: invalidate map
15970 }
15971
15972
15973 void
15974 ForwardEvent ()
15975 {
15976     if (gameMode == IcsExamining && !pausing) {
15977         SendToICS(ics_prefix);
15978         SendToICS("forward\n");
15979     } else {
15980         ForwardInner(currentMove + 1);
15981     }
15982 }
15983
15984 void
15985 ToEndEvent ()
15986 {
15987     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15988         /* to optimze, we temporarily turn off analysis mode while we feed
15989          * the remaining moves to the engine. Otherwise we get analysis output
15990          * after each move.
15991          */
15992         if (first.analysisSupport) {
15993           SendToProgram("exit\nforce\n", &first);
15994           first.analyzing = FALSE;
15995         }
15996     }
15997
15998     if (gameMode == IcsExamining && !pausing) {
15999         SendToICS(ics_prefix);
16000         SendToICS("forward 999999\n");
16001     } else {
16002         ForwardInner(forwardMostMove);
16003     }
16004
16005     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16006         /* we have fed all the moves, so reactivate analysis mode */
16007         SendToProgram("analyze\n", &first);
16008         first.analyzing = TRUE;
16009         /*first.maybeThinking = TRUE;*/
16010         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16011     }
16012 }
16013
16014 void
16015 BackwardInner (int target)
16016 {
16017     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16018
16019     if (appData.debugMode)
16020         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16021                 target, currentMove, forwardMostMove);
16022
16023     if (gameMode == EditPosition) return;
16024     seekGraphUp = FALSE;
16025     MarkTargetSquares(1);
16026     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16027     if (currentMove <= backwardMostMove) {
16028         ClearHighlights();
16029         DrawPosition(full_redraw, boards[currentMove]);
16030         return;
16031     }
16032     if (gameMode == PlayFromGameFile && !pausing)
16033       PauseEvent();
16034
16035     if (moveList[target][0]) {
16036         int fromX, fromY, toX, toY;
16037         toX = moveList[target][2] - AAA;
16038         toY = moveList[target][3] - ONE;
16039         if (moveList[target][1] == '@') {
16040             if (appData.highlightLastMove) {
16041                 SetHighlights(-1, -1, toX, toY);
16042             }
16043         } else {
16044             fromX = moveList[target][0] - AAA;
16045             fromY = moveList[target][1] - ONE;
16046             if (target == currentMove - 1) {
16047                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16048             }
16049             if (appData.highlightLastMove) {
16050                 SetHighlights(fromX, fromY, toX, toY);
16051             }
16052         }
16053     }
16054     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16055         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16056         while (currentMove > target) {
16057             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16058                 // null move cannot be undone. Reload program with move history before it.
16059                 int i;
16060                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16061                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16062                 }
16063                 SendBoard(&first, i);
16064               if(second.analyzing) SendBoard(&second, i);
16065                 for(currentMove=i; currentMove<target; currentMove++) {
16066                     SendMoveToProgram(currentMove, &first);
16067                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16068                 }
16069                 break;
16070             }
16071             SendToBoth("undo\n");
16072             currentMove--;
16073         }
16074     } else {
16075         currentMove = target;
16076     }
16077
16078     if (gameMode == EditGame || gameMode == EndOfGame) {
16079         whiteTimeRemaining = timeRemaining[0][currentMove];
16080         blackTimeRemaining = timeRemaining[1][currentMove];
16081     }
16082     DisplayBothClocks();
16083     DisplayMove(currentMove - 1);
16084     DrawPosition(full_redraw, boards[currentMove]);
16085     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16086     // [HGM] PV info: routine tests if comment empty
16087     DisplayComment(currentMove - 1, commentList[currentMove]);
16088     ClearMap(); // [HGM] exclude: invalidate map
16089 }
16090
16091 void
16092 BackwardEvent ()
16093 {
16094     if (gameMode == IcsExamining && !pausing) {
16095         SendToICS(ics_prefix);
16096         SendToICS("backward\n");
16097     } else {
16098         BackwardInner(currentMove - 1);
16099     }
16100 }
16101
16102 void
16103 ToStartEvent ()
16104 {
16105     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16106         /* to optimize, we temporarily turn off analysis mode while we undo
16107          * all the moves. Otherwise we get analysis output after each undo.
16108          */
16109         if (first.analysisSupport) {
16110           SendToProgram("exit\nforce\n", &first);
16111           first.analyzing = FALSE;
16112         }
16113     }
16114
16115     if (gameMode == IcsExamining && !pausing) {
16116         SendToICS(ics_prefix);
16117         SendToICS("backward 999999\n");
16118     } else {
16119         BackwardInner(backwardMostMove);
16120     }
16121
16122     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16123         /* we have fed all the moves, so reactivate analysis mode */
16124         SendToProgram("analyze\n", &first);
16125         first.analyzing = TRUE;
16126         /*first.maybeThinking = TRUE;*/
16127         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16128     }
16129 }
16130
16131 void
16132 ToNrEvent (int to)
16133 {
16134   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16135   if (to >= forwardMostMove) to = forwardMostMove;
16136   if (to <= backwardMostMove) to = backwardMostMove;
16137   if (to < currentMove) {
16138     BackwardInner(to);
16139   } else {
16140     ForwardInner(to);
16141   }
16142 }
16143
16144 void
16145 RevertEvent (Boolean annotate)
16146 {
16147     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16148         return;
16149     }
16150     if (gameMode != IcsExamining) {
16151         DisplayError(_("You are not examining a game"), 0);
16152         return;
16153     }
16154     if (pausing) {
16155         DisplayError(_("You can't revert while pausing"), 0);
16156         return;
16157     }
16158     SendToICS(ics_prefix);
16159     SendToICS("revert\n");
16160 }
16161
16162 void
16163 RetractMoveEvent ()
16164 {
16165     switch (gameMode) {
16166       case MachinePlaysWhite:
16167       case MachinePlaysBlack:
16168         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16169             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16170             return;
16171         }
16172         if (forwardMostMove < 2) return;
16173         currentMove = forwardMostMove = forwardMostMove - 2;
16174         whiteTimeRemaining = timeRemaining[0][currentMove];
16175         blackTimeRemaining = timeRemaining[1][currentMove];
16176         DisplayBothClocks();
16177         DisplayMove(currentMove - 1);
16178         ClearHighlights();/*!! could figure this out*/
16179         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16180         SendToProgram("remove\n", &first);
16181         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16182         break;
16183
16184       case BeginningOfGame:
16185       default:
16186         break;
16187
16188       case IcsPlayingWhite:
16189       case IcsPlayingBlack:
16190         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16191             SendToICS(ics_prefix);
16192             SendToICS("takeback 2\n");
16193         } else {
16194             SendToICS(ics_prefix);
16195             SendToICS("takeback 1\n");
16196         }
16197         break;
16198     }
16199 }
16200
16201 void
16202 MoveNowEvent ()
16203 {
16204     ChessProgramState *cps;
16205
16206     switch (gameMode) {
16207       case MachinePlaysWhite:
16208         if (!WhiteOnMove(forwardMostMove)) {
16209             DisplayError(_("It is your turn"), 0);
16210             return;
16211         }
16212         cps = &first;
16213         break;
16214       case MachinePlaysBlack:
16215         if (WhiteOnMove(forwardMostMove)) {
16216             DisplayError(_("It is your turn"), 0);
16217             return;
16218         }
16219         cps = &first;
16220         break;
16221       case TwoMachinesPlay:
16222         if (WhiteOnMove(forwardMostMove) ==
16223             (first.twoMachinesColor[0] == 'w')) {
16224             cps = &first;
16225         } else {
16226             cps = &second;
16227         }
16228         break;
16229       case BeginningOfGame:
16230       default:
16231         return;
16232     }
16233     SendToProgram("?\n", cps);
16234 }
16235
16236 void
16237 TruncateGameEvent ()
16238 {
16239     EditGameEvent();
16240     if (gameMode != EditGame) return;
16241     TruncateGame();
16242 }
16243
16244 void
16245 TruncateGame ()
16246 {
16247     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16248     if (forwardMostMove > currentMove) {
16249         if (gameInfo.resultDetails != NULL) {
16250             free(gameInfo.resultDetails);
16251             gameInfo.resultDetails = NULL;
16252             gameInfo.result = GameUnfinished;
16253         }
16254         forwardMostMove = currentMove;
16255         HistorySet(parseList, backwardMostMove, forwardMostMove,
16256                    currentMove-1);
16257     }
16258 }
16259
16260 void
16261 HintEvent ()
16262 {
16263     if (appData.noChessProgram) return;
16264     switch (gameMode) {
16265       case MachinePlaysWhite:
16266         if (WhiteOnMove(forwardMostMove)) {
16267             DisplayError(_("Wait until your turn."), 0);
16268             return;
16269         }
16270         break;
16271       case BeginningOfGame:
16272       case MachinePlaysBlack:
16273         if (!WhiteOnMove(forwardMostMove)) {
16274             DisplayError(_("Wait until your turn."), 0);
16275             return;
16276         }
16277         break;
16278       default:
16279         DisplayError(_("No hint available"), 0);
16280         return;
16281     }
16282     SendToProgram("hint\n", &first);
16283     hintRequested = TRUE;
16284 }
16285
16286 int
16287 SaveSelected (FILE *g, int dummy, char *dummy2)
16288 {
16289     ListGame * lg = (ListGame *) gameList.head;
16290     int nItem, cnt=0;
16291     FILE *f;
16292
16293     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16294         DisplayError(_("Game list not loaded or empty"), 0);
16295         return 0;
16296     }
16297
16298     creatingBook = TRUE; // suppresses stuff during load game
16299
16300     /* Get list size */
16301     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16302         if(lg->position >= 0) { // selected?
16303             LoadGame(f, nItem, "", TRUE);
16304             SaveGamePGN2(g); // leaves g open
16305             cnt++; DoEvents();
16306         }
16307         lg = (ListGame *) lg->node.succ;
16308     }
16309
16310     fclose(g);
16311     creatingBook = FALSE;
16312
16313     return cnt;
16314 }
16315
16316 void
16317 CreateBookEvent ()
16318 {
16319     ListGame * lg = (ListGame *) gameList.head;
16320     FILE *f, *g;
16321     int nItem;
16322     static int secondTime = FALSE;
16323
16324     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16325         DisplayError(_("Game list not loaded or empty"), 0);
16326         return;
16327     }
16328
16329     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16330         fclose(g);
16331         secondTime++;
16332         DisplayNote(_("Book file exists! Try again for overwrite."));
16333         return;
16334     }
16335
16336     creatingBook = TRUE;
16337     secondTime = FALSE;
16338
16339     /* Get list size */
16340     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16341         if(lg->position >= 0) {
16342             LoadGame(f, nItem, "", TRUE);
16343             AddGameToBook(TRUE);
16344             DoEvents();
16345         }
16346         lg = (ListGame *) lg->node.succ;
16347     }
16348
16349     creatingBook = FALSE;
16350     FlushBook();
16351 }
16352
16353 void
16354 BookEvent ()
16355 {
16356     if (appData.noChessProgram) return;
16357     switch (gameMode) {
16358       case MachinePlaysWhite:
16359         if (WhiteOnMove(forwardMostMove)) {
16360             DisplayError(_("Wait until your turn."), 0);
16361             return;
16362         }
16363         break;
16364       case BeginningOfGame:
16365       case MachinePlaysBlack:
16366         if (!WhiteOnMove(forwardMostMove)) {
16367             DisplayError(_("Wait until your turn."), 0);
16368             return;
16369         }
16370         break;
16371       case EditPosition:
16372         EditPositionDone(TRUE);
16373         break;
16374       case TwoMachinesPlay:
16375         return;
16376       default:
16377         break;
16378     }
16379     SendToProgram("bk\n", &first);
16380     bookOutput[0] = NULLCHAR;
16381     bookRequested = TRUE;
16382 }
16383
16384 void
16385 AboutGameEvent ()
16386 {
16387     char *tags = PGNTags(&gameInfo);
16388     TagsPopUp(tags, CmailMsg());
16389     free(tags);
16390 }
16391
16392 /* end button procedures */
16393
16394 void
16395 PrintPosition (FILE *fp, int move)
16396 {
16397     int i, j;
16398
16399     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16400         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16401             char c = PieceToChar(boards[move][i][j]);
16402             fputc(c == '?' ? '.' : c, fp);
16403             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16404         }
16405     }
16406     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16407       fprintf(fp, "white to play\n");
16408     else
16409       fprintf(fp, "black to play\n");
16410 }
16411
16412 void
16413 PrintOpponents (FILE *fp)
16414 {
16415     if (gameInfo.white != NULL) {
16416         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16417     } else {
16418         fprintf(fp, "\n");
16419     }
16420 }
16421
16422 /* Find last component of program's own name, using some heuristics */
16423 void
16424 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16425 {
16426     char *p, *q, c;
16427     int local = (strcmp(host, "localhost") == 0);
16428     while (!local && (p = strchr(prog, ';')) != NULL) {
16429         p++;
16430         while (*p == ' ') p++;
16431         prog = p;
16432     }
16433     if (*prog == '"' || *prog == '\'') {
16434         q = strchr(prog + 1, *prog);
16435     } else {
16436         q = strchr(prog, ' ');
16437     }
16438     if (q == NULL) q = prog + strlen(prog);
16439     p = q;
16440     while (p >= prog && *p != '/' && *p != '\\') p--;
16441     p++;
16442     if(p == prog && *p == '"') p++;
16443     c = *q; *q = 0;
16444     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16445     memcpy(buf, p, q - p);
16446     buf[q - p] = NULLCHAR;
16447     if (!local) {
16448         strcat(buf, "@");
16449         strcat(buf, host);
16450     }
16451 }
16452
16453 char *
16454 TimeControlTagValue ()
16455 {
16456     char buf[MSG_SIZ];
16457     if (!appData.clockMode) {
16458       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16459     } else if (movesPerSession > 0) {
16460       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16461     } else if (timeIncrement == 0) {
16462       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16463     } else {
16464       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16465     }
16466     return StrSave(buf);
16467 }
16468
16469 void
16470 SetGameInfo ()
16471 {
16472     /* This routine is used only for certain modes */
16473     VariantClass v = gameInfo.variant;
16474     ChessMove r = GameUnfinished;
16475     char *p = NULL;
16476
16477     if(keepInfo) return;
16478
16479     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16480         r = gameInfo.result;
16481         p = gameInfo.resultDetails;
16482         gameInfo.resultDetails = NULL;
16483     }
16484     ClearGameInfo(&gameInfo);
16485     gameInfo.variant = v;
16486
16487     switch (gameMode) {
16488       case MachinePlaysWhite:
16489         gameInfo.event = StrSave( appData.pgnEventHeader );
16490         gameInfo.site = StrSave(HostName());
16491         gameInfo.date = PGNDate();
16492         gameInfo.round = StrSave("-");
16493         gameInfo.white = StrSave(first.tidy);
16494         gameInfo.black = StrSave(UserName());
16495         gameInfo.timeControl = TimeControlTagValue();
16496         break;
16497
16498       case MachinePlaysBlack:
16499         gameInfo.event = StrSave( appData.pgnEventHeader );
16500         gameInfo.site = StrSave(HostName());
16501         gameInfo.date = PGNDate();
16502         gameInfo.round = StrSave("-");
16503         gameInfo.white = StrSave(UserName());
16504         gameInfo.black = StrSave(first.tidy);
16505         gameInfo.timeControl = TimeControlTagValue();
16506         break;
16507
16508       case TwoMachinesPlay:
16509         gameInfo.event = StrSave( appData.pgnEventHeader );
16510         gameInfo.site = StrSave(HostName());
16511         gameInfo.date = PGNDate();
16512         if (roundNr > 0) {
16513             char buf[MSG_SIZ];
16514             snprintf(buf, MSG_SIZ, "%d", roundNr);
16515             gameInfo.round = StrSave(buf);
16516         } else {
16517             gameInfo.round = StrSave("-");
16518         }
16519         if (first.twoMachinesColor[0] == 'w') {
16520             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16521             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16522         } else {
16523             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16524             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16525         }
16526         gameInfo.timeControl = TimeControlTagValue();
16527         break;
16528
16529       case EditGame:
16530         gameInfo.event = StrSave("Edited game");
16531         gameInfo.site = StrSave(HostName());
16532         gameInfo.date = PGNDate();
16533         gameInfo.round = StrSave("-");
16534         gameInfo.white = StrSave("-");
16535         gameInfo.black = StrSave("-");
16536         gameInfo.result = r;
16537         gameInfo.resultDetails = p;
16538         break;
16539
16540       case EditPosition:
16541         gameInfo.event = StrSave("Edited position");
16542         gameInfo.site = StrSave(HostName());
16543         gameInfo.date = PGNDate();
16544         gameInfo.round = StrSave("-");
16545         gameInfo.white = StrSave("-");
16546         gameInfo.black = StrSave("-");
16547         break;
16548
16549       case IcsPlayingWhite:
16550       case IcsPlayingBlack:
16551       case IcsObserving:
16552       case IcsExamining:
16553         break;
16554
16555       case PlayFromGameFile:
16556         gameInfo.event = StrSave("Game from non-PGN file");
16557         gameInfo.site = StrSave(HostName());
16558         gameInfo.date = PGNDate();
16559         gameInfo.round = StrSave("-");
16560         gameInfo.white = StrSave("?");
16561         gameInfo.black = StrSave("?");
16562         break;
16563
16564       default:
16565         break;
16566     }
16567 }
16568
16569 void
16570 ReplaceComment (int index, char *text)
16571 {
16572     int len;
16573     char *p;
16574     float score;
16575
16576     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16577        pvInfoList[index-1].depth == len &&
16578        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16579        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16580     while (*text == '\n') text++;
16581     len = strlen(text);
16582     while (len > 0 && text[len - 1] == '\n') len--;
16583
16584     if (commentList[index] != NULL)
16585       free(commentList[index]);
16586
16587     if (len == 0) {
16588         commentList[index] = NULL;
16589         return;
16590     }
16591   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16592       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16593       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16594     commentList[index] = (char *) malloc(len + 2);
16595     strncpy(commentList[index], text, len);
16596     commentList[index][len] = '\n';
16597     commentList[index][len + 1] = NULLCHAR;
16598   } else {
16599     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16600     char *p;
16601     commentList[index] = (char *) malloc(len + 7);
16602     safeStrCpy(commentList[index], "{\n", 3);
16603     safeStrCpy(commentList[index]+2, text, len+1);
16604     commentList[index][len+2] = NULLCHAR;
16605     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16606     strcat(commentList[index], "\n}\n");
16607   }
16608 }
16609
16610 void
16611 CrushCRs (char *text)
16612 {
16613   char *p = text;
16614   char *q = text;
16615   char ch;
16616
16617   do {
16618     ch = *p++;
16619     if (ch == '\r') continue;
16620     *q++ = ch;
16621   } while (ch != '\0');
16622 }
16623
16624 void
16625 AppendComment (int index, char *text, Boolean addBraces)
16626 /* addBraces  tells if we should add {} */
16627 {
16628     int oldlen, len;
16629     char *old;
16630
16631 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16632     if(addBraces == 3) addBraces = 0; else // force appending literally
16633     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16634
16635     CrushCRs(text);
16636     while (*text == '\n') text++;
16637     len = strlen(text);
16638     while (len > 0 && text[len - 1] == '\n') len--;
16639     text[len] = NULLCHAR;
16640
16641     if (len == 0) return;
16642
16643     if (commentList[index] != NULL) {
16644       Boolean addClosingBrace = addBraces;
16645         old = commentList[index];
16646         oldlen = strlen(old);
16647         while(commentList[index][oldlen-1] ==  '\n')
16648           commentList[index][--oldlen] = NULLCHAR;
16649         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16650         safeStrCpy(commentList[index], old, oldlen + len + 6);
16651         free(old);
16652         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16653         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16654           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16655           while (*text == '\n') { text++; len--; }
16656           commentList[index][--oldlen] = NULLCHAR;
16657       }
16658         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16659         else          strcat(commentList[index], "\n");
16660         strcat(commentList[index], text);
16661         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16662         else          strcat(commentList[index], "\n");
16663     } else {
16664         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16665         if(addBraces)
16666           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16667         else commentList[index][0] = NULLCHAR;
16668         strcat(commentList[index], text);
16669         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16670         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16671     }
16672 }
16673
16674 static char *
16675 FindStr (char * text, char * sub_text)
16676 {
16677     char * result = strstr( text, sub_text );
16678
16679     if( result != NULL ) {
16680         result += strlen( sub_text );
16681     }
16682
16683     return result;
16684 }
16685
16686 /* [AS] Try to extract PV info from PGN comment */
16687 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16688 char *
16689 GetInfoFromComment (int index, char * text)
16690 {
16691     char * sep = text, *p;
16692
16693     if( text != NULL && index > 0 ) {
16694         int score = 0;
16695         int depth = 0;
16696         int time = -1, sec = 0, deci;
16697         char * s_eval = FindStr( text, "[%eval " );
16698         char * s_emt = FindStr( text, "[%emt " );
16699 #if 0
16700         if( s_eval != NULL || s_emt != NULL ) {
16701 #else
16702         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16703 #endif
16704             /* New style */
16705             char delim;
16706
16707             if( s_eval != NULL ) {
16708                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16709                     return text;
16710                 }
16711
16712                 if( delim != ']' ) {
16713                     return text;
16714                 }
16715             }
16716
16717             if( s_emt != NULL ) {
16718             }
16719                 return text;
16720         }
16721         else {
16722             /* We expect something like: [+|-]nnn.nn/dd */
16723             int score_lo = 0;
16724
16725             if(*text != '{') return text; // [HGM] braces: must be normal comment
16726
16727             sep = strchr( text, '/' );
16728             if( sep == NULL || sep < (text+4) ) {
16729                 return text;
16730             }
16731
16732             p = text;
16733             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16734             if(p[1] == '(') { // comment starts with PV
16735                p = strchr(p, ')'); // locate end of PV
16736                if(p == NULL || sep < p+5) return text;
16737                // at this point we have something like "{(.*) +0.23/6 ..."
16738                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16739                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16740                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16741             }
16742             time = -1; sec = -1; deci = -1;
16743             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16744                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16745                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16746                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16747                 return text;
16748             }
16749
16750             if( score_lo < 0 || score_lo >= 100 ) {
16751                 return text;
16752             }
16753
16754             if(sec >= 0) time = 600*time + 10*sec; else
16755             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16756
16757             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16758
16759             /* [HGM] PV time: now locate end of PV info */
16760             while( *++sep >= '0' && *sep <= '9'); // strip depth
16761             if(time >= 0)
16762             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16763             if(sec >= 0)
16764             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16765             if(deci >= 0)
16766             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16767             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16768         }
16769
16770         if( depth <= 0 ) {
16771             return text;
16772         }
16773
16774         if( time < 0 ) {
16775             time = -1;
16776         }
16777
16778         pvInfoList[index-1].depth = depth;
16779         pvInfoList[index-1].score = score;
16780         pvInfoList[index-1].time  = 10*time; // centi-sec
16781         if(*sep == '}') *sep = 0; else *--sep = '{';
16782         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16783     }
16784     return sep;
16785 }
16786
16787 void
16788 SendToProgram (char *message, ChessProgramState *cps)
16789 {
16790     int count, outCount, error;
16791     char buf[MSG_SIZ];
16792
16793     if (cps->pr == NoProc) return;
16794     Attention(cps);
16795
16796     if (appData.debugMode) {
16797         TimeMark now;
16798         GetTimeMark(&now);
16799         fprintf(debugFP, "%ld >%-6s: %s",
16800                 SubtractTimeMarks(&now, &programStartTime),
16801                 cps->which, message);
16802         if(serverFP)
16803             fprintf(serverFP, "%ld >%-6s: %s",
16804                 SubtractTimeMarks(&now, &programStartTime),
16805                 cps->which, message), fflush(serverFP);
16806     }
16807
16808     count = strlen(message);
16809     outCount = OutputToProcess(cps->pr, message, count, &error);
16810     if (outCount < count && !exiting
16811                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16812       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16813       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16814         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16815             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16816                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16817                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16818                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16819             } else {
16820                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16821                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16822                 gameInfo.result = res;
16823             }
16824             gameInfo.resultDetails = StrSave(buf);
16825         }
16826         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16827         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16828     }
16829 }
16830
16831 void
16832 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16833 {
16834     char *end_str;
16835     char buf[MSG_SIZ];
16836     ChessProgramState *cps = (ChessProgramState *)closure;
16837
16838     if (isr != cps->isr) return; /* Killed intentionally */
16839     if (count <= 0) {
16840         if (count == 0) {
16841             RemoveInputSource(cps->isr);
16842             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16843                     _(cps->which), cps->program);
16844             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16845             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16846                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16847                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16848                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16849                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16850                 } else {
16851                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16852                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16853                     gameInfo.result = res;
16854                 }
16855                 gameInfo.resultDetails = StrSave(buf);
16856             }
16857             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16858             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16859         } else {
16860             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16861                     _(cps->which), cps->program);
16862             RemoveInputSource(cps->isr);
16863
16864             /* [AS] Program is misbehaving badly... kill it */
16865             if( count == -2 ) {
16866                 DestroyChildProcess( cps->pr, 9 );
16867                 cps->pr = NoProc;
16868             }
16869
16870             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16871         }
16872         return;
16873     }
16874
16875     if ((end_str = strchr(message, '\r')) != NULL)
16876       *end_str = NULLCHAR;
16877     if ((end_str = strchr(message, '\n')) != NULL)
16878       *end_str = NULLCHAR;
16879
16880     if (appData.debugMode) {
16881         TimeMark now; int print = 1;
16882         char *quote = ""; char c; int i;
16883
16884         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16885                 char start = message[0];
16886                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16887                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16888                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16889                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16890                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16891                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16892                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16893                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16894                    sscanf(message, "hint: %c", &c)!=1 &&
16895                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16896                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16897                     print = (appData.engineComments >= 2);
16898                 }
16899                 message[0] = start; // restore original message
16900         }
16901         if(print) {
16902                 GetTimeMark(&now);
16903                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16904                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16905                         quote,
16906                         message);
16907                 if(serverFP)
16908                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16909                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16910                         quote,
16911                         message), fflush(serverFP);
16912         }
16913     }
16914
16915     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16916     if (appData.icsEngineAnalyze) {
16917         if (strstr(message, "whisper") != NULL ||
16918              strstr(message, "kibitz") != NULL ||
16919             strstr(message, "tellics") != NULL) return;
16920     }
16921
16922     HandleMachineMove(message, cps);
16923 }
16924
16925
16926 void
16927 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16928 {
16929     char buf[MSG_SIZ];
16930     int seconds;
16931
16932     if( timeControl_2 > 0 ) {
16933         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16934             tc = timeControl_2;
16935         }
16936     }
16937     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16938     inc /= cps->timeOdds;
16939     st  /= cps->timeOdds;
16940
16941     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16942
16943     if (st > 0) {
16944       /* Set exact time per move, normally using st command */
16945       if (cps->stKludge) {
16946         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16947         seconds = st % 60;
16948         if (seconds == 0) {
16949           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16950         } else {
16951           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16952         }
16953       } else {
16954         snprintf(buf, MSG_SIZ, "st %d\n", st);
16955       }
16956     } else {
16957       /* Set conventional or incremental time control, using level command */
16958       if (seconds == 0) {
16959         /* Note old gnuchess bug -- minutes:seconds used to not work.
16960            Fixed in later versions, but still avoid :seconds
16961            when seconds is 0. */
16962         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16963       } else {
16964         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16965                  seconds, inc/1000.);
16966       }
16967     }
16968     SendToProgram(buf, cps);
16969
16970     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16971     /* Orthogonally, limit search to given depth */
16972     if (sd > 0) {
16973       if (cps->sdKludge) {
16974         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16975       } else {
16976         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16977       }
16978       SendToProgram(buf, cps);
16979     }
16980
16981     if(cps->nps >= 0) { /* [HGM] nps */
16982         if(cps->supportsNPS == FALSE)
16983           cps->nps = -1; // don't use if engine explicitly says not supported!
16984         else {
16985           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16986           SendToProgram(buf, cps);
16987         }
16988     }
16989 }
16990
16991 ChessProgramState *
16992 WhitePlayer ()
16993 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16994 {
16995     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16996        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16997         return &second;
16998     return &first;
16999 }
17000
17001 void
17002 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17003 {
17004     char message[MSG_SIZ];
17005     long time, otime;
17006
17007     /* Note: this routine must be called when the clocks are stopped
17008        or when they have *just* been set or switched; otherwise
17009        it will be off by the time since the current tick started.
17010     */
17011     if (machineWhite) {
17012         time = whiteTimeRemaining / 10;
17013         otime = blackTimeRemaining / 10;
17014     } else {
17015         time = blackTimeRemaining / 10;
17016         otime = whiteTimeRemaining / 10;
17017     }
17018     /* [HGM] translate opponent's time by time-odds factor */
17019     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17020
17021     if (time <= 0) time = 1;
17022     if (otime <= 0) otime = 1;
17023
17024     snprintf(message, MSG_SIZ, "time %ld\n", time);
17025     SendToProgram(message, cps);
17026
17027     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17028     SendToProgram(message, cps);
17029 }
17030
17031 char *
17032 EngineDefinedVariant (ChessProgramState *cps, int n)
17033 {   // return name of n-th unknown variant that engine supports
17034     static char buf[MSG_SIZ];
17035     char *p, *s = cps->variants;
17036     if(!s) return NULL;
17037     do { // parse string from variants feature
17038       VariantClass v;
17039         p = strchr(s, ',');
17040         if(p) *p = NULLCHAR;
17041       v = StringToVariant(s);
17042       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17043         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17044             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17045                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17046                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17047                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17048             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17049         }
17050         if(p) *p++ = ',';
17051         if(n < 0) return buf;
17052     } while(s = p);
17053     return NULL;
17054 }
17055
17056 int
17057 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17058 {
17059   char buf[MSG_SIZ];
17060   int len = strlen(name);
17061   int val;
17062
17063   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17064     (*p) += len + 1;
17065     sscanf(*p, "%d", &val);
17066     *loc = (val != 0);
17067     while (**p && **p != ' ')
17068       (*p)++;
17069     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17070     SendToProgram(buf, cps);
17071     return TRUE;
17072   }
17073   return FALSE;
17074 }
17075
17076 int
17077 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17078 {
17079   char buf[MSG_SIZ];
17080   int len = strlen(name);
17081   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17082     (*p) += len + 1;
17083     sscanf(*p, "%d", loc);
17084     while (**p && **p != ' ') (*p)++;
17085     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17086     SendToProgram(buf, cps);
17087     return TRUE;
17088   }
17089   return FALSE;
17090 }
17091
17092 int
17093 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17094 {
17095   char buf[MSG_SIZ];
17096   int len = strlen(name);
17097   if (strncmp((*p), name, len) == 0
17098       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17099     (*p) += len + 2;
17100     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17101     sscanf(*p, "%[^\"]", *loc);
17102     while (**p && **p != '\"') (*p)++;
17103     if (**p == '\"') (*p)++;
17104     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17105     SendToProgram(buf, cps);
17106     return TRUE;
17107   }
17108   return FALSE;
17109 }
17110
17111 int
17112 ParseOption (Option *opt, ChessProgramState *cps)
17113 // [HGM] options: process the string that defines an engine option, and determine
17114 // name, type, default value, and allowed value range
17115 {
17116         char *p, *q, buf[MSG_SIZ];
17117         int n, min = (-1)<<31, max = 1<<31, def;
17118
17119         opt->target = &opt->value;   // OK for spin/slider and checkbox
17120         if(p = strstr(opt->name, " -spin ")) {
17121             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17122             if(max < min) max = min; // enforce consistency
17123             if(def < min) def = min;
17124             if(def > max) def = max;
17125             opt->value = def;
17126             opt->min = min;
17127             opt->max = max;
17128             opt->type = Spin;
17129         } else if((p = strstr(opt->name, " -slider "))) {
17130             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17131             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17132             if(max < min) max = min; // enforce consistency
17133             if(def < min) def = min;
17134             if(def > max) def = max;
17135             opt->value = def;
17136             opt->min = min;
17137             opt->max = max;
17138             opt->type = Spin; // Slider;
17139         } else if((p = strstr(opt->name, " -string "))) {
17140             opt->textValue = p+9;
17141             opt->type = TextBox;
17142             opt->target = &opt->textValue;
17143         } else if((p = strstr(opt->name, " -file "))) {
17144             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17145             opt->target = opt->textValue = p+7;
17146             opt->type = FileName; // FileName;
17147             opt->target = &opt->textValue;
17148         } else if((p = strstr(opt->name, " -path "))) {
17149             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17150             opt->target = opt->textValue = p+7;
17151             opt->type = PathName; // PathName;
17152             opt->target = &opt->textValue;
17153         } else if(p = strstr(opt->name, " -check ")) {
17154             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17155             opt->value = (def != 0);
17156             opt->type = CheckBox;
17157         } else if(p = strstr(opt->name, " -combo ")) {
17158             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17159             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17160             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17161             opt->value = n = 0;
17162             while(q = StrStr(q, " /// ")) {
17163                 n++; *q = 0;    // count choices, and null-terminate each of them
17164                 q += 5;
17165                 if(*q == '*') { // remember default, which is marked with * prefix
17166                     q++;
17167                     opt->value = n;
17168                 }
17169                 cps->comboList[cps->comboCnt++] = q;
17170             }
17171             cps->comboList[cps->comboCnt++] = NULL;
17172             opt->max = n + 1;
17173             opt->type = ComboBox;
17174         } else if(p = strstr(opt->name, " -button")) {
17175             opt->type = Button;
17176         } else if(p = strstr(opt->name, " -save")) {
17177             opt->type = SaveButton;
17178         } else return FALSE;
17179         *p = 0; // terminate option name
17180         // now look if the command-line options define a setting for this engine option.
17181         if(cps->optionSettings && cps->optionSettings[0])
17182             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17183         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17184           snprintf(buf, MSG_SIZ, "option %s", p);
17185                 if(p = strstr(buf, ",")) *p = 0;
17186                 if(q = strchr(buf, '=')) switch(opt->type) {
17187                     case ComboBox:
17188                         for(n=0; n<opt->max; n++)
17189                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17190                         break;
17191                     case TextBox:
17192                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17193                         break;
17194                     case Spin:
17195                     case CheckBox:
17196                         opt->value = atoi(q+1);
17197                     default:
17198                         break;
17199                 }
17200                 strcat(buf, "\n");
17201                 SendToProgram(buf, cps);
17202         }
17203         return TRUE;
17204 }
17205
17206 void
17207 FeatureDone (ChessProgramState *cps, int val)
17208 {
17209   DelayedEventCallback cb = GetDelayedEvent();
17210   if ((cb == InitBackEnd3 && cps == &first) ||
17211       (cb == SettingsMenuIfReady && cps == &second) ||
17212       (cb == LoadEngine) ||
17213       (cb == TwoMachinesEventIfReady)) {
17214     CancelDelayedEvent();
17215     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17216   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17217   cps->initDone = val;
17218   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17219 }
17220
17221 /* Parse feature command from engine */
17222 void
17223 ParseFeatures (char *args, ChessProgramState *cps)
17224 {
17225   char *p = args;
17226   char *q = NULL;
17227   int val;
17228   char buf[MSG_SIZ];
17229
17230   for (;;) {
17231     while (*p == ' ') p++;
17232     if (*p == NULLCHAR) return;
17233
17234     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17235     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17236     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17237     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17238     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17239     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17240     if (BoolFeature(&p, "reuse", &val, cps)) {
17241       /* Engine can disable reuse, but can't enable it if user said no */
17242       if (!val) cps->reuse = FALSE;
17243       continue;
17244     }
17245     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17246     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17247       if (gameMode == TwoMachinesPlay) {
17248         DisplayTwoMachinesTitle();
17249       } else {
17250         DisplayTitle("");
17251       }
17252       continue;
17253     }
17254     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17255     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17256     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17257     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17258     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17259     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17260     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17261     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17262     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17263     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17264     if (IntFeature(&p, "done", &val, cps)) {
17265       FeatureDone(cps, val);
17266       continue;
17267     }
17268     /* Added by Tord: */
17269     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17270     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17271     /* End of additions by Tord */
17272
17273     /* [HGM] added features: */
17274     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17275     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17276     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17277     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17278     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17279     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17280     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17281     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17282         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17283         FREE(cps->option[cps->nrOptions].name);
17284         cps->option[cps->nrOptions].name = q; q = NULL;
17285         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17286           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17287             SendToProgram(buf, cps);
17288             continue;
17289         }
17290         if(cps->nrOptions >= MAX_OPTIONS) {
17291             cps->nrOptions--;
17292             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17293             DisplayError(buf, 0);
17294         }
17295         continue;
17296     }
17297     /* End of additions by HGM */
17298
17299     /* unknown feature: complain and skip */
17300     q = p;
17301     while (*q && *q != '=') q++;
17302     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17303     SendToProgram(buf, cps);
17304     p = q;
17305     if (*p == '=') {
17306       p++;
17307       if (*p == '\"') {
17308         p++;
17309         while (*p && *p != '\"') p++;
17310         if (*p == '\"') p++;
17311       } else {
17312         while (*p && *p != ' ') p++;
17313       }
17314     }
17315   }
17316
17317 }
17318
17319 void
17320 PeriodicUpdatesEvent (int newState)
17321 {
17322     if (newState == appData.periodicUpdates)
17323       return;
17324
17325     appData.periodicUpdates=newState;
17326
17327     /* Display type changes, so update it now */
17328 //    DisplayAnalysis();
17329
17330     /* Get the ball rolling again... */
17331     if (newState) {
17332         AnalysisPeriodicEvent(1);
17333         StartAnalysisClock();
17334     }
17335 }
17336
17337 void
17338 PonderNextMoveEvent (int newState)
17339 {
17340     if (newState == appData.ponderNextMove) return;
17341     if (gameMode == EditPosition) EditPositionDone(TRUE);
17342     if (newState) {
17343         SendToProgram("hard\n", &first);
17344         if (gameMode == TwoMachinesPlay) {
17345             SendToProgram("hard\n", &second);
17346         }
17347     } else {
17348         SendToProgram("easy\n", &first);
17349         thinkOutput[0] = NULLCHAR;
17350         if (gameMode == TwoMachinesPlay) {
17351             SendToProgram("easy\n", &second);
17352         }
17353     }
17354     appData.ponderNextMove = newState;
17355 }
17356
17357 void
17358 NewSettingEvent (int option, int *feature, char *command, int value)
17359 {
17360     char buf[MSG_SIZ];
17361
17362     if (gameMode == EditPosition) EditPositionDone(TRUE);
17363     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17364     if(feature == NULL || *feature) SendToProgram(buf, &first);
17365     if (gameMode == TwoMachinesPlay) {
17366         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17367     }
17368 }
17369
17370 void
17371 ShowThinkingEvent ()
17372 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17373 {
17374     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17375     int newState = appData.showThinking
17376         // [HGM] thinking: other features now need thinking output as well
17377         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17378
17379     if (oldState == newState) return;
17380     oldState = newState;
17381     if (gameMode == EditPosition) EditPositionDone(TRUE);
17382     if (oldState) {
17383         SendToProgram("post\n", &first);
17384         if (gameMode == TwoMachinesPlay) {
17385             SendToProgram("post\n", &second);
17386         }
17387     } else {
17388         SendToProgram("nopost\n", &first);
17389         thinkOutput[0] = NULLCHAR;
17390         if (gameMode == TwoMachinesPlay) {
17391             SendToProgram("nopost\n", &second);
17392         }
17393     }
17394 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17395 }
17396
17397 void
17398 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17399 {
17400   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17401   if (pr == NoProc) return;
17402   AskQuestion(title, question, replyPrefix, pr);
17403 }
17404
17405 void
17406 TypeInEvent (char firstChar)
17407 {
17408     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17409         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17410         gameMode == AnalyzeMode || gameMode == EditGame ||
17411         gameMode == EditPosition || gameMode == IcsExamining ||
17412         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17413         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17414                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17415                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17416         gameMode == Training) PopUpMoveDialog(firstChar);
17417 }
17418
17419 void
17420 TypeInDoneEvent (char *move)
17421 {
17422         Board board;
17423         int n, fromX, fromY, toX, toY;
17424         char promoChar;
17425         ChessMove moveType;
17426
17427         // [HGM] FENedit
17428         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17429                 EditPositionPasteFEN(move);
17430                 return;
17431         }
17432         // [HGM] movenum: allow move number to be typed in any mode
17433         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17434           ToNrEvent(2*n-1);
17435           return;
17436         }
17437         // undocumented kludge: allow command-line option to be typed in!
17438         // (potentially fatal, and does not implement the effect of the option.)
17439         // should only be used for options that are values on which future decisions will be made,
17440         // and definitely not on options that would be used during initialization.
17441         if(strstr(move, "!!! -") == move) {
17442             ParseArgsFromString(move+4);
17443             return;
17444         }
17445
17446       if (gameMode != EditGame && currentMove != forwardMostMove &&
17447         gameMode != Training) {
17448         DisplayMoveError(_("Displayed move is not current"));
17449       } else {
17450         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17451           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17452         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17453         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17454           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17455           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17456         } else {
17457           DisplayMoveError(_("Could not parse move"));
17458         }
17459       }
17460 }
17461
17462 void
17463 DisplayMove (int moveNumber)
17464 {
17465     char message[MSG_SIZ];
17466     char res[MSG_SIZ];
17467     char cpThinkOutput[MSG_SIZ];
17468
17469     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17470
17471     if (moveNumber == forwardMostMove - 1 ||
17472         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17473
17474         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17475
17476         if (strchr(cpThinkOutput, '\n')) {
17477             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17478         }
17479     } else {
17480         *cpThinkOutput = NULLCHAR;
17481     }
17482
17483     /* [AS] Hide thinking from human user */
17484     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17485         *cpThinkOutput = NULLCHAR;
17486         if( thinkOutput[0] != NULLCHAR ) {
17487             int i;
17488
17489             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17490                 cpThinkOutput[i] = '.';
17491             }
17492             cpThinkOutput[i] = NULLCHAR;
17493             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17494         }
17495     }
17496
17497     if (moveNumber == forwardMostMove - 1 &&
17498         gameInfo.resultDetails != NULL) {
17499         if (gameInfo.resultDetails[0] == NULLCHAR) {
17500           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17501         } else {
17502           snprintf(res, MSG_SIZ, " {%s} %s",
17503                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17504         }
17505     } else {
17506         res[0] = NULLCHAR;
17507     }
17508
17509     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17510         DisplayMessage(res, cpThinkOutput);
17511     } else {
17512       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17513                 WhiteOnMove(moveNumber) ? " " : ".. ",
17514                 parseList[moveNumber], res);
17515         DisplayMessage(message, cpThinkOutput);
17516     }
17517 }
17518
17519 void
17520 DisplayComment (int moveNumber, char *text)
17521 {
17522     char title[MSG_SIZ];
17523
17524     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17525       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17526     } else {
17527       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17528               WhiteOnMove(moveNumber) ? " " : ".. ",
17529               parseList[moveNumber]);
17530     }
17531     if (text != NULL && (appData.autoDisplayComment || commentUp))
17532         CommentPopUp(title, text);
17533 }
17534
17535 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17536  * might be busy thinking or pondering.  It can be omitted if your
17537  * gnuchess is configured to stop thinking immediately on any user
17538  * input.  However, that gnuchess feature depends on the FIONREAD
17539  * ioctl, which does not work properly on some flavors of Unix.
17540  */
17541 void
17542 Attention (ChessProgramState *cps)
17543 {
17544 #if ATTENTION
17545     if (!cps->useSigint) return;
17546     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17547     switch (gameMode) {
17548       case MachinePlaysWhite:
17549       case MachinePlaysBlack:
17550       case TwoMachinesPlay:
17551       case IcsPlayingWhite:
17552       case IcsPlayingBlack:
17553       case AnalyzeMode:
17554       case AnalyzeFile:
17555         /* Skip if we know it isn't thinking */
17556         if (!cps->maybeThinking) return;
17557         if (appData.debugMode)
17558           fprintf(debugFP, "Interrupting %s\n", cps->which);
17559         InterruptChildProcess(cps->pr);
17560         cps->maybeThinking = FALSE;
17561         break;
17562       default:
17563         break;
17564     }
17565 #endif /*ATTENTION*/
17566 }
17567
17568 int
17569 CheckFlags ()
17570 {
17571     if (whiteTimeRemaining <= 0) {
17572         if (!whiteFlag) {
17573             whiteFlag = TRUE;
17574             if (appData.icsActive) {
17575                 if (appData.autoCallFlag &&
17576                     gameMode == IcsPlayingBlack && !blackFlag) {
17577                   SendToICS(ics_prefix);
17578                   SendToICS("flag\n");
17579                 }
17580             } else {
17581                 if (blackFlag) {
17582                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17583                 } else {
17584                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17585                     if (appData.autoCallFlag) {
17586                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17587                         return TRUE;
17588                     }
17589                 }
17590             }
17591         }
17592     }
17593     if (blackTimeRemaining <= 0) {
17594         if (!blackFlag) {
17595             blackFlag = TRUE;
17596             if (appData.icsActive) {
17597                 if (appData.autoCallFlag &&
17598                     gameMode == IcsPlayingWhite && !whiteFlag) {
17599                   SendToICS(ics_prefix);
17600                   SendToICS("flag\n");
17601                 }
17602             } else {
17603                 if (whiteFlag) {
17604                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17605                 } else {
17606                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17607                     if (appData.autoCallFlag) {
17608                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17609                         return TRUE;
17610                     }
17611                 }
17612             }
17613         }
17614     }
17615     return FALSE;
17616 }
17617
17618 void
17619 CheckTimeControl ()
17620 {
17621     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17622         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17623
17624     /*
17625      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17626      */
17627     if ( !WhiteOnMove(forwardMostMove) ) {
17628         /* White made time control */
17629         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17630         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17631         /* [HGM] time odds: correct new time quota for time odds! */
17632                                             / WhitePlayer()->timeOdds;
17633         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17634     } else {
17635         lastBlack -= blackTimeRemaining;
17636         /* Black made time control */
17637         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17638                                             / WhitePlayer()->other->timeOdds;
17639         lastWhite = whiteTimeRemaining;
17640     }
17641 }
17642
17643 void
17644 DisplayBothClocks ()
17645 {
17646     int wom = gameMode == EditPosition ?
17647       !blackPlaysFirst : WhiteOnMove(currentMove);
17648     DisplayWhiteClock(whiteTimeRemaining, wom);
17649     DisplayBlackClock(blackTimeRemaining, !wom);
17650 }
17651
17652
17653 /* Timekeeping seems to be a portability nightmare.  I think everyone
17654    has ftime(), but I'm really not sure, so I'm including some ifdefs
17655    to use other calls if you don't.  Clocks will be less accurate if
17656    you have neither ftime nor gettimeofday.
17657 */
17658
17659 /* VS 2008 requires the #include outside of the function */
17660 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17661 #include <sys/timeb.h>
17662 #endif
17663
17664 /* Get the current time as a TimeMark */
17665 void
17666 GetTimeMark (TimeMark *tm)
17667 {
17668 #if HAVE_GETTIMEOFDAY
17669
17670     struct timeval timeVal;
17671     struct timezone timeZone;
17672
17673     gettimeofday(&timeVal, &timeZone);
17674     tm->sec = (long) timeVal.tv_sec;
17675     tm->ms = (int) (timeVal.tv_usec / 1000L);
17676
17677 #else /*!HAVE_GETTIMEOFDAY*/
17678 #if HAVE_FTIME
17679
17680 // include <sys/timeb.h> / moved to just above start of function
17681     struct timeb timeB;
17682
17683     ftime(&timeB);
17684     tm->sec = (long) timeB.time;
17685     tm->ms = (int) timeB.millitm;
17686
17687 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17688     tm->sec = (long) time(NULL);
17689     tm->ms = 0;
17690 #endif
17691 #endif
17692 }
17693
17694 /* Return the difference in milliseconds between two
17695    time marks.  We assume the difference will fit in a long!
17696 */
17697 long
17698 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17699 {
17700     return 1000L*(tm2->sec - tm1->sec) +
17701            (long) (tm2->ms - tm1->ms);
17702 }
17703
17704
17705 /*
17706  * Code to manage the game clocks.
17707  *
17708  * In tournament play, black starts the clock and then white makes a move.
17709  * We give the human user a slight advantage if he is playing white---the
17710  * clocks don't run until he makes his first move, so it takes zero time.
17711  * Also, we don't account for network lag, so we could get out of sync
17712  * with GNU Chess's clock -- but then, referees are always right.
17713  */
17714
17715 static TimeMark tickStartTM;
17716 static long intendedTickLength;
17717
17718 long
17719 NextTickLength (long timeRemaining)
17720 {
17721     long nominalTickLength, nextTickLength;
17722
17723     if (timeRemaining > 0L && timeRemaining <= 10000L)
17724       nominalTickLength = 100L;
17725     else
17726       nominalTickLength = 1000L;
17727     nextTickLength = timeRemaining % nominalTickLength;
17728     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17729
17730     return nextTickLength;
17731 }
17732
17733 /* Adjust clock one minute up or down */
17734 void
17735 AdjustClock (Boolean which, int dir)
17736 {
17737     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17738     if(which) blackTimeRemaining += 60000*dir;
17739     else      whiteTimeRemaining += 60000*dir;
17740     DisplayBothClocks();
17741     adjustedClock = TRUE;
17742 }
17743
17744 /* Stop clocks and reset to a fresh time control */
17745 void
17746 ResetClocks ()
17747 {
17748     (void) StopClockTimer();
17749     if (appData.icsActive) {
17750         whiteTimeRemaining = blackTimeRemaining = 0;
17751     } else if (searchTime) {
17752         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17753         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17754     } else { /* [HGM] correct new time quote for time odds */
17755         whiteTC = blackTC = fullTimeControlString;
17756         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17757         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17758     }
17759     if (whiteFlag || blackFlag) {
17760         DisplayTitle("");
17761         whiteFlag = blackFlag = FALSE;
17762     }
17763     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17764     DisplayBothClocks();
17765     adjustedClock = FALSE;
17766 }
17767
17768 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17769
17770 /* Decrement running clock by amount of time that has passed */
17771 void
17772 DecrementClocks ()
17773 {
17774     long timeRemaining;
17775     long lastTickLength, fudge;
17776     TimeMark now;
17777
17778     if (!appData.clockMode) return;
17779     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17780
17781     GetTimeMark(&now);
17782
17783     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17784
17785     /* Fudge if we woke up a little too soon */
17786     fudge = intendedTickLength - lastTickLength;
17787     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17788
17789     if (WhiteOnMove(forwardMostMove)) {
17790         if(whiteNPS >= 0) lastTickLength = 0;
17791         timeRemaining = whiteTimeRemaining -= lastTickLength;
17792         if(timeRemaining < 0 && !appData.icsActive) {
17793             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17794             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17795                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17796                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17797             }
17798         }
17799         DisplayWhiteClock(whiteTimeRemaining - fudge,
17800                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17801     } else {
17802         if(blackNPS >= 0) lastTickLength = 0;
17803         timeRemaining = blackTimeRemaining -= lastTickLength;
17804         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17805             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17806             if(suddenDeath) {
17807                 blackStartMove = forwardMostMove;
17808                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17809             }
17810         }
17811         DisplayBlackClock(blackTimeRemaining - fudge,
17812                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17813     }
17814     if (CheckFlags()) return;
17815
17816     if(twoBoards) { // count down secondary board's clocks as well
17817         activePartnerTime -= lastTickLength;
17818         partnerUp = 1;
17819         if(activePartner == 'W')
17820             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17821         else
17822             DisplayBlackClock(activePartnerTime, TRUE);
17823         partnerUp = 0;
17824     }
17825
17826     tickStartTM = now;
17827     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17828     StartClockTimer(intendedTickLength);
17829
17830     /* if the time remaining has fallen below the alarm threshold, sound the
17831      * alarm. if the alarm has sounded and (due to a takeback or time control
17832      * with increment) the time remaining has increased to a level above the
17833      * threshold, reset the alarm so it can sound again.
17834      */
17835
17836     if (appData.icsActive && appData.icsAlarm) {
17837
17838         /* make sure we are dealing with the user's clock */
17839         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17840                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17841            )) return;
17842
17843         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17844             alarmSounded = FALSE;
17845         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17846             PlayAlarmSound();
17847             alarmSounded = TRUE;
17848         }
17849     }
17850 }
17851
17852
17853 /* A player has just moved, so stop the previously running
17854    clock and (if in clock mode) start the other one.
17855    We redisplay both clocks in case we're in ICS mode, because
17856    ICS gives us an update to both clocks after every move.
17857    Note that this routine is called *after* forwardMostMove
17858    is updated, so the last fractional tick must be subtracted
17859    from the color that is *not* on move now.
17860 */
17861 void
17862 SwitchClocks (int newMoveNr)
17863 {
17864     long lastTickLength;
17865     TimeMark now;
17866     int flagged = FALSE;
17867
17868     GetTimeMark(&now);
17869
17870     if (StopClockTimer() && appData.clockMode) {
17871         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17872         if (!WhiteOnMove(forwardMostMove)) {
17873             if(blackNPS >= 0) lastTickLength = 0;
17874             blackTimeRemaining -= lastTickLength;
17875            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17876 //         if(pvInfoList[forwardMostMove].time == -1)
17877                  pvInfoList[forwardMostMove].time =               // use GUI time
17878                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17879         } else {
17880            if(whiteNPS >= 0) lastTickLength = 0;
17881            whiteTimeRemaining -= lastTickLength;
17882            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17883 //         if(pvInfoList[forwardMostMove].time == -1)
17884                  pvInfoList[forwardMostMove].time =
17885                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17886         }
17887         flagged = CheckFlags();
17888     }
17889     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17890     CheckTimeControl();
17891
17892     if (flagged || !appData.clockMode) return;
17893
17894     switch (gameMode) {
17895       case MachinePlaysBlack:
17896       case MachinePlaysWhite:
17897       case BeginningOfGame:
17898         if (pausing) return;
17899         break;
17900
17901       case EditGame:
17902       case PlayFromGameFile:
17903       case IcsExamining:
17904         return;
17905
17906       default:
17907         break;
17908     }
17909
17910     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17911         if(WhiteOnMove(forwardMostMove))
17912              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17913         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17914     }
17915
17916     tickStartTM = now;
17917     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17918       whiteTimeRemaining : blackTimeRemaining);
17919     StartClockTimer(intendedTickLength);
17920 }
17921
17922
17923 /* Stop both clocks */
17924 void
17925 StopClocks ()
17926 {
17927     long lastTickLength;
17928     TimeMark now;
17929
17930     if (!StopClockTimer()) return;
17931     if (!appData.clockMode) return;
17932
17933     GetTimeMark(&now);
17934
17935     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17936     if (WhiteOnMove(forwardMostMove)) {
17937         if(whiteNPS >= 0) lastTickLength = 0;
17938         whiteTimeRemaining -= lastTickLength;
17939         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17940     } else {
17941         if(blackNPS >= 0) lastTickLength = 0;
17942         blackTimeRemaining -= lastTickLength;
17943         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17944     }
17945     CheckFlags();
17946 }
17947
17948 /* Start clock of player on move.  Time may have been reset, so
17949    if clock is already running, stop and restart it. */
17950 void
17951 StartClocks ()
17952 {
17953     (void) StopClockTimer(); /* in case it was running already */
17954     DisplayBothClocks();
17955     if (CheckFlags()) return;
17956
17957     if (!appData.clockMode) return;
17958     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17959
17960     GetTimeMark(&tickStartTM);
17961     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17962       whiteTimeRemaining : blackTimeRemaining);
17963
17964    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17965     whiteNPS = blackNPS = -1;
17966     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17967        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17968         whiteNPS = first.nps;
17969     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17970        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17971         blackNPS = first.nps;
17972     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17973         whiteNPS = second.nps;
17974     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17975         blackNPS = second.nps;
17976     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17977
17978     StartClockTimer(intendedTickLength);
17979 }
17980
17981 char *
17982 TimeString (long ms)
17983 {
17984     long second, minute, hour, day;
17985     char *sign = "";
17986     static char buf[32];
17987
17988     if (ms > 0 && ms <= 9900) {
17989       /* convert milliseconds to tenths, rounding up */
17990       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17991
17992       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17993       return buf;
17994     }
17995
17996     /* convert milliseconds to seconds, rounding up */
17997     /* use floating point to avoid strangeness of integer division
17998        with negative dividends on many machines */
17999     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18000
18001     if (second < 0) {
18002         sign = "-";
18003         second = -second;
18004     }
18005
18006     day = second / (60 * 60 * 24);
18007     second = second % (60 * 60 * 24);
18008     hour = second / (60 * 60);
18009     second = second % (60 * 60);
18010     minute = second / 60;
18011     second = second % 60;
18012
18013     if (day > 0)
18014       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18015               sign, day, hour, minute, second);
18016     else if (hour > 0)
18017       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18018     else
18019       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18020
18021     return buf;
18022 }
18023
18024
18025 /*
18026  * This is necessary because some C libraries aren't ANSI C compliant yet.
18027  */
18028 char *
18029 StrStr (char *string, char *match)
18030 {
18031     int i, length;
18032
18033     length = strlen(match);
18034
18035     for (i = strlen(string) - length; i >= 0; i--, string++)
18036       if (!strncmp(match, string, length))
18037         return string;
18038
18039     return NULL;
18040 }
18041
18042 char *
18043 StrCaseStr (char *string, char *match)
18044 {
18045     int i, j, length;
18046
18047     length = strlen(match);
18048
18049     for (i = strlen(string) - length; i >= 0; i--, string++) {
18050         for (j = 0; j < length; j++) {
18051             if (ToLower(match[j]) != ToLower(string[j]))
18052               break;
18053         }
18054         if (j == length) return string;
18055     }
18056
18057     return NULL;
18058 }
18059
18060 #ifndef _amigados
18061 int
18062 StrCaseCmp (char *s1, char *s2)
18063 {
18064     char c1, c2;
18065
18066     for (;;) {
18067         c1 = ToLower(*s1++);
18068         c2 = ToLower(*s2++);
18069         if (c1 > c2) return 1;
18070         if (c1 < c2) return -1;
18071         if (c1 == NULLCHAR) return 0;
18072     }
18073 }
18074
18075
18076 int
18077 ToLower (int c)
18078 {
18079     return isupper(c) ? tolower(c) : c;
18080 }
18081
18082
18083 int
18084 ToUpper (int c)
18085 {
18086     return islower(c) ? toupper(c) : c;
18087 }
18088 #endif /* !_amigados    */
18089
18090 char *
18091 StrSave (char *s)
18092 {
18093   char *ret;
18094
18095   if ((ret = (char *) malloc(strlen(s) + 1)))
18096     {
18097       safeStrCpy(ret, s, strlen(s)+1);
18098     }
18099   return ret;
18100 }
18101
18102 char *
18103 StrSavePtr (char *s, char **savePtr)
18104 {
18105     if (*savePtr) {
18106         free(*savePtr);
18107     }
18108     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18109       safeStrCpy(*savePtr, s, strlen(s)+1);
18110     }
18111     return(*savePtr);
18112 }
18113
18114 char *
18115 PGNDate ()
18116 {
18117     time_t clock;
18118     struct tm *tm;
18119     char buf[MSG_SIZ];
18120
18121     clock = time((time_t *)NULL);
18122     tm = localtime(&clock);
18123     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18124             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18125     return StrSave(buf);
18126 }
18127
18128
18129 char *
18130 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18131 {
18132     int i, j, fromX, fromY, toX, toY;
18133     int whiteToPlay, haveRights = nrCastlingRights;
18134     char buf[MSG_SIZ];
18135     char *p, *q;
18136     int emptycount;
18137     ChessSquare piece;
18138
18139     whiteToPlay = (gameMode == EditPosition) ?
18140       !blackPlaysFirst : (move % 2 == 0);
18141     p = buf;
18142
18143     /* Piece placement data */
18144     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18145         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18146         emptycount = 0;
18147         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18148             if (boards[move][i][j] == EmptySquare) {
18149                 emptycount++;
18150             } else { ChessSquare piece = boards[move][i][j];
18151                 if (emptycount > 0) {
18152                     if(emptycount<10) /* [HGM] can be >= 10 */
18153                         *p++ = '0' + emptycount;
18154                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18155                     emptycount = 0;
18156                 }
18157                 if(PieceToChar(piece) == '+') {
18158                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18159                     *p++ = '+';
18160                     piece = (ChessSquare)(CHUDEMOTED(piece));
18161                 }
18162                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18163                 if(*p = PieceSuffix(piece)) p++;
18164                 if(p[-1] == '~') {
18165                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18166                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18167                     *p++ = '~';
18168                 }
18169             }
18170         }
18171         if (emptycount > 0) {
18172             if(emptycount<10) /* [HGM] can be >= 10 */
18173                 *p++ = '0' + emptycount;
18174             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18175             emptycount = 0;
18176         }
18177         *p++ = '/';
18178     }
18179     *(p - 1) = ' ';
18180
18181     /* [HGM] print Crazyhouse or Shogi holdings */
18182     if( gameInfo.holdingsWidth ) {
18183         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18184         q = p;
18185         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18186             piece = boards[move][i][BOARD_WIDTH-1];
18187             if( piece != EmptySquare )
18188               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18189                   *p++ = PieceToChar(piece);
18190         }
18191         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18192             piece = boards[move][BOARD_HEIGHT-i-1][0];
18193             if( piece != EmptySquare )
18194               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18195                   *p++ = PieceToChar(piece);
18196         }
18197
18198         if( q == p ) *p++ = '-';
18199         *p++ = ']';
18200         *p++ = ' ';
18201     }
18202
18203     /* Active color */
18204     *p++ = whiteToPlay ? 'w' : 'b';
18205     *p++ = ' ';
18206
18207   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18208     haveRights = 0; q = p;
18209     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18210       piece = boards[move][0][i];
18211       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18212         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18213       }
18214     }
18215     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18216       piece = boards[move][BOARD_HEIGHT-1][i];
18217       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18218         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18219       }
18220     }
18221     if(p == q) *p++ = '-';
18222     *p++ = ' ';
18223   }
18224
18225   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18226     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18227   } else {
18228   if(haveRights) {
18229      int handW=0, handB=0;
18230      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18231         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18232         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18233      }
18234      q = p;
18235      if(appData.fischerCastling) {
18236         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18237            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18238                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18239         } else {
18240        /* [HGM] write directly from rights */
18241            if(boards[move][CASTLING][2] != NoRights &&
18242               boards[move][CASTLING][0] != NoRights   )
18243                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18244            if(boards[move][CASTLING][2] != NoRights &&
18245               boards[move][CASTLING][1] != NoRights   )
18246                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18247         }
18248         if(handB) {
18249            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18250                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18251         } else {
18252            if(boards[move][CASTLING][5] != NoRights &&
18253               boards[move][CASTLING][3] != NoRights   )
18254                 *p++ = boards[move][CASTLING][3] + AAA;
18255            if(boards[move][CASTLING][5] != NoRights &&
18256               boards[move][CASTLING][4] != NoRights   )
18257                 *p++ = boards[move][CASTLING][4] + AAA;
18258         }
18259      } else {
18260
18261         /* [HGM] write true castling rights */
18262         if( nrCastlingRights == 6 ) {
18263             int q, k=0;
18264             if(boards[move][CASTLING][0] != NoRights &&
18265                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18266             q = (boards[move][CASTLING][1] != NoRights &&
18267                  boards[move][CASTLING][2] != NoRights  );
18268             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18269                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18270                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18271                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18272             }
18273             if(q) *p++ = 'Q';
18274             k = 0;
18275             if(boards[move][CASTLING][3] != NoRights &&
18276                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18277             q = (boards[move][CASTLING][4] != NoRights &&
18278                  boards[move][CASTLING][5] != NoRights  );
18279             if(handB) {
18280                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18281                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18282                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18283             }
18284             if(q) *p++ = 'q';
18285         }
18286      }
18287      if (q == p) *p++ = '-'; /* No castling rights */
18288      *p++ = ' ';
18289   }
18290
18291   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18292      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18293      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18294     /* En passant target square */
18295     if (move > backwardMostMove) {
18296         fromX = moveList[move - 1][0] - AAA;
18297         fromY = moveList[move - 1][1] - ONE;
18298         toX = moveList[move - 1][2] - AAA;
18299         toY = moveList[move - 1][3] - ONE;
18300         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18301             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18302             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18303             fromX == toX) {
18304             /* 2-square pawn move just happened */
18305             *p++ = toX + AAA;
18306             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18307         } else {
18308             *p++ = '-';
18309         }
18310     } else if(move == backwardMostMove) {
18311         // [HGM] perhaps we should always do it like this, and forget the above?
18312         if((signed char)boards[move][EP_STATUS] >= 0) {
18313             *p++ = boards[move][EP_STATUS] + AAA;
18314             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18315         } else {
18316             *p++ = '-';
18317         }
18318     } else {
18319         *p++ = '-';
18320     }
18321     *p++ = ' ';
18322   }
18323   }
18324
18325     if(moveCounts)
18326     {   int i = 0, j=move;
18327
18328         /* [HGM] find reversible plies */
18329         if (appData.debugMode) { int k;
18330             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18331             for(k=backwardMostMove; k<=forwardMostMove; k++)
18332                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18333
18334         }
18335
18336         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18337         if( j == backwardMostMove ) i += initialRulePlies;
18338         sprintf(p, "%d ", i);
18339         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18340
18341         /* Fullmove number */
18342         sprintf(p, "%d", (move / 2) + 1);
18343     } else *--p = NULLCHAR;
18344
18345     return StrSave(buf);
18346 }
18347
18348 Boolean
18349 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18350 {
18351     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18352     char *p, c;
18353     int emptycount, virgin[BOARD_FILES];
18354     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18355
18356     p = fen;
18357
18358     /* Piece placement data */
18359     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18360         j = 0;
18361         for (;;) {
18362             if (*p == '/' || *p == ' ' || *p == '[' ) {
18363                 if(j > w) w = j;
18364                 emptycount = gameInfo.boardWidth - j;
18365                 while (emptycount--)
18366                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18367                 if (*p == '/') p++;
18368                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18369                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18370                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18371                     }
18372                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18373                 }
18374                 break;
18375 #if(BOARD_FILES >= 10)*0
18376             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18377                 p++; emptycount=10;
18378                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18379                 while (emptycount--)
18380                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18381 #endif
18382             } else if (*p == '*') {
18383                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18384             } else if (isdigit(*p)) {
18385                 emptycount = *p++ - '0';
18386                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18387                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18388                 while (emptycount--)
18389                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18390             } else if (*p == '<') {
18391                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18392                 else if (i != 0 || !shuffle) return FALSE;
18393                 p++;
18394             } else if (shuffle && *p == '>') {
18395                 p++; // for now ignore closing shuffle range, and assume rank-end
18396             } else if (*p == '?') {
18397                 if (j >= gameInfo.boardWidth) return FALSE;
18398                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18399                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18400             } else if (*p == '+' || isalpha(*p)) {
18401                 char *q, *s = SUFFIXES;
18402                 if (j >= gameInfo.boardWidth) return FALSE;
18403                 if(*p=='+') {
18404                     char c = *++p;
18405                     if(q = strchr(s, p[1])) p++;
18406                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18407                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18408                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18409                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18410                 } else {
18411                     char c = *p++;
18412                     if(q = strchr(s, *p)) p++;
18413                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18414                 }
18415
18416                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18417                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18418                     piece = (ChessSquare) (PROMOTED(piece));
18419                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18420                     p++;
18421                 }
18422                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18423                 if(piece == king) wKingRank = i;
18424                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18425             } else {
18426                 return FALSE;
18427             }
18428         }
18429     }
18430     while (*p == '/' || *p == ' ') p++;
18431
18432     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18433
18434     /* [HGM] by default clear Crazyhouse holdings, if present */
18435     if(gameInfo.holdingsWidth) {
18436        for(i=0; i<BOARD_HEIGHT; i++) {
18437            board[i][0]             = EmptySquare; /* black holdings */
18438            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18439            board[i][1]             = (ChessSquare) 0; /* black counts */
18440            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18441        }
18442     }
18443
18444     /* [HGM] look for Crazyhouse holdings here */
18445     while(*p==' ') p++;
18446     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18447         int swap=0, wcnt=0, bcnt=0;
18448         if(*p == '[') p++;
18449         if(*p == '<') swap++, p++;
18450         if(*p == '-' ) p++; /* empty holdings */ else {
18451             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18452             /* if we would allow FEN reading to set board size, we would   */
18453             /* have to add holdings and shift the board read so far here   */
18454             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18455                 p++;
18456                 if((int) piece >= (int) BlackPawn ) {
18457                     i = (int)piece - (int)BlackPawn;
18458                     i = PieceToNumber((ChessSquare)i);
18459                     if( i >= gameInfo.holdingsSize ) return FALSE;
18460                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18461                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18462                     bcnt++;
18463                 } else {
18464                     i = (int)piece - (int)WhitePawn;
18465                     i = PieceToNumber((ChessSquare)i);
18466                     if( i >= gameInfo.holdingsSize ) return FALSE;
18467                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18468                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18469                     wcnt++;
18470                 }
18471             }
18472             if(subst) { // substitute back-rank question marks by holdings pieces
18473                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18474                     int k, m, n = bcnt + 1;
18475                     if(board[0][j] == ClearBoard) {
18476                         if(!wcnt) return FALSE;
18477                         n = rand() % wcnt;
18478                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18479                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18480                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18481                             break;
18482                         }
18483                     }
18484                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18485                         if(!bcnt) return FALSE;
18486                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18487                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18488                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18489                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18490                             break;
18491                         }
18492                     }
18493                 }
18494                 subst = 0;
18495             }
18496         }
18497         if(*p == ']') p++;
18498     }
18499
18500     if(subst) return FALSE; // substitution requested, but no holdings
18501
18502     while(*p == ' ') p++;
18503
18504     /* Active color */
18505     c = *p++;
18506     if(appData.colorNickNames) {
18507       if( c == appData.colorNickNames[0] ) c = 'w'; else
18508       if( c == appData.colorNickNames[1] ) c = 'b';
18509     }
18510     switch (c) {
18511       case 'w':
18512         *blackPlaysFirst = FALSE;
18513         break;
18514       case 'b':
18515         *blackPlaysFirst = TRUE;
18516         break;
18517       default:
18518         return FALSE;
18519     }
18520
18521     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18522     /* return the extra info in global variiables             */
18523
18524     while(*p==' ') p++;
18525
18526     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18527         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18528         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18529     }
18530
18531     /* set defaults in case FEN is incomplete */
18532     board[EP_STATUS] = EP_UNKNOWN;
18533     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18534     for(i=0; i<nrCastlingRights; i++ ) {
18535         board[CASTLING][i] =
18536             appData.fischerCastling ? NoRights : initialRights[i];
18537     }   /* assume possible unless obviously impossible */
18538     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18539     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18540     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18541                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18542     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18543     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18544     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18545                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18546     FENrulePlies = 0;
18547
18548     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18549       char *q = p;
18550       int w=0, b=0;
18551       while(isalpha(*p)) {
18552         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18553         if(islower(*p)) b |= 1 << (*p++ - 'a');
18554       }
18555       if(*p == '-') p++;
18556       if(p != q) {
18557         board[TOUCHED_W] = ~w;
18558         board[TOUCHED_B] = ~b;
18559         while(*p == ' ') p++;
18560       }
18561     } else
18562
18563     if(nrCastlingRights) {
18564       int fischer = 0;
18565       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18566       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18567           /* castling indicator present, so default becomes no castlings */
18568           for(i=0; i<nrCastlingRights; i++ ) {
18569                  board[CASTLING][i] = NoRights;
18570           }
18571       }
18572       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18573              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18574              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18575              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18576         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18577
18578         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18579             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18580             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18581         }
18582         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18583             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18584         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18585                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18586         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18587                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18588         switch(c) {
18589           case'K':
18590               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18591               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18592               board[CASTLING][2] = whiteKingFile;
18593               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18594               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18595               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18596               break;
18597           case'Q':
18598               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18599               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18600               board[CASTLING][2] = whiteKingFile;
18601               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18602               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18603               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18604               break;
18605           case'k':
18606               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18607               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18608               board[CASTLING][5] = blackKingFile;
18609               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18610               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18611               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18612               break;
18613           case'q':
18614               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18615               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18616               board[CASTLING][5] = blackKingFile;
18617               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18618               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18619               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18620           case '-':
18621               break;
18622           default: /* FRC castlings */
18623               if(c >= 'a') { /* black rights */
18624                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18625                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18626                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18627                   if(i == BOARD_RGHT) break;
18628                   board[CASTLING][5] = i;
18629                   c -= AAA;
18630                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18631                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18632                   if(c > i)
18633                       board[CASTLING][3] = c;
18634                   else
18635                       board[CASTLING][4] = c;
18636               } else { /* white rights */
18637                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18638                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18639                     if(board[0][i] == WhiteKing) break;
18640                   if(i == BOARD_RGHT) break;
18641                   board[CASTLING][2] = i;
18642                   c -= AAA - 'a' + 'A';
18643                   if(board[0][c] >= WhiteKing) break;
18644                   if(c > i)
18645                       board[CASTLING][0] = c;
18646                   else
18647                       board[CASTLING][1] = c;
18648               }
18649         }
18650       }
18651       for(i=0; i<nrCastlingRights; i++)
18652         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18653       if(gameInfo.variant == VariantSChess)
18654         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18655       if(fischer && shuffle) appData.fischerCastling = TRUE;
18656     if (appData.debugMode) {
18657         fprintf(debugFP, "FEN castling rights:");
18658         for(i=0; i<nrCastlingRights; i++)
18659         fprintf(debugFP, " %d", board[CASTLING][i]);
18660         fprintf(debugFP, "\n");
18661     }
18662
18663       while(*p==' ') p++;
18664     }
18665
18666     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18667
18668     /* read e.p. field in games that know e.p. capture */
18669     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18670        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18671        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18672       if(*p=='-') {
18673         p++; board[EP_STATUS] = EP_NONE;
18674       } else {
18675          char c = *p++ - AAA;
18676
18677          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18678          if(*p >= '0' && *p <='9') p++;
18679          board[EP_STATUS] = c;
18680       }
18681     }
18682
18683
18684     if(sscanf(p, "%d", &i) == 1) {
18685         FENrulePlies = i; /* 50-move ply counter */
18686         /* (The move number is still ignored)    */
18687     }
18688
18689     return TRUE;
18690 }
18691
18692 void
18693 EditPositionPasteFEN (char *fen)
18694 {
18695   if (fen != NULL) {
18696     Board initial_position;
18697
18698     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18699       DisplayError(_("Bad FEN position in clipboard"), 0);
18700       return ;
18701     } else {
18702       int savedBlackPlaysFirst = blackPlaysFirst;
18703       EditPositionEvent();
18704       blackPlaysFirst = savedBlackPlaysFirst;
18705       CopyBoard(boards[0], initial_position);
18706       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18707       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18708       DisplayBothClocks();
18709       DrawPosition(FALSE, boards[currentMove]);
18710     }
18711   }
18712 }
18713
18714 static char cseq[12] = "\\   ";
18715
18716 Boolean
18717 set_cont_sequence (char *new_seq)
18718 {
18719     int len;
18720     Boolean ret;
18721
18722     // handle bad attempts to set the sequence
18723         if (!new_seq)
18724                 return 0; // acceptable error - no debug
18725
18726     len = strlen(new_seq);
18727     ret = (len > 0) && (len < sizeof(cseq));
18728     if (ret)
18729       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18730     else if (appData.debugMode)
18731       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18732     return ret;
18733 }
18734
18735 /*
18736     reformat a source message so words don't cross the width boundary.  internal
18737     newlines are not removed.  returns the wrapped size (no null character unless
18738     included in source message).  If dest is NULL, only calculate the size required
18739     for the dest buffer.  lp argument indicats line position upon entry, and it's
18740     passed back upon exit.
18741 */
18742 int
18743 wrap (char *dest, char *src, int count, int width, int *lp)
18744 {
18745     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18746
18747     cseq_len = strlen(cseq);
18748     old_line = line = *lp;
18749     ansi = len = clen = 0;
18750
18751     for (i=0; i < count; i++)
18752     {
18753         if (src[i] == '\033')
18754             ansi = 1;
18755
18756         // if we hit the width, back up
18757         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18758         {
18759             // store i & len in case the word is too long
18760             old_i = i, old_len = len;
18761
18762             // find the end of the last word
18763             while (i && src[i] != ' ' && src[i] != '\n')
18764             {
18765                 i--;
18766                 len--;
18767             }
18768
18769             // word too long?  restore i & len before splitting it
18770             if ((old_i-i+clen) >= width)
18771             {
18772                 i = old_i;
18773                 len = old_len;
18774             }
18775
18776             // extra space?
18777             if (i && src[i-1] == ' ')
18778                 len--;
18779
18780             if (src[i] != ' ' && src[i] != '\n')
18781             {
18782                 i--;
18783                 if (len)
18784                     len--;
18785             }
18786
18787             // now append the newline and continuation sequence
18788             if (dest)
18789                 dest[len] = '\n';
18790             len++;
18791             if (dest)
18792                 strncpy(dest+len, cseq, cseq_len);
18793             len += cseq_len;
18794             line = cseq_len;
18795             clen = cseq_len;
18796             continue;
18797         }
18798
18799         if (dest)
18800             dest[len] = src[i];
18801         len++;
18802         if (!ansi)
18803             line++;
18804         if (src[i] == '\n')
18805             line = 0;
18806         if (src[i] == 'm')
18807             ansi = 0;
18808     }
18809     if (dest && appData.debugMode)
18810     {
18811         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18812             count, width, line, len, *lp);
18813         show_bytes(debugFP, src, count);
18814         fprintf(debugFP, "\ndest: ");
18815         show_bytes(debugFP, dest, len);
18816         fprintf(debugFP, "\n");
18817     }
18818     *lp = dest ? line : old_line;
18819
18820     return len;
18821 }
18822
18823 // [HGM] vari: routines for shelving variations
18824 Boolean modeRestore = FALSE;
18825
18826 void
18827 PushInner (int firstMove, int lastMove)
18828 {
18829         int i, j, nrMoves = lastMove - firstMove;
18830
18831         // push current tail of game on stack
18832         savedResult[storedGames] = gameInfo.result;
18833         savedDetails[storedGames] = gameInfo.resultDetails;
18834         gameInfo.resultDetails = NULL;
18835         savedFirst[storedGames] = firstMove;
18836         savedLast [storedGames] = lastMove;
18837         savedFramePtr[storedGames] = framePtr;
18838         framePtr -= nrMoves; // reserve space for the boards
18839         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18840             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18841             for(j=0; j<MOVE_LEN; j++)
18842                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18843             for(j=0; j<2*MOVE_LEN; j++)
18844                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18845             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18846             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18847             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18848             pvInfoList[firstMove+i-1].depth = 0;
18849             commentList[framePtr+i] = commentList[firstMove+i];
18850             commentList[firstMove+i] = NULL;
18851         }
18852
18853         storedGames++;
18854         forwardMostMove = firstMove; // truncate game so we can start variation
18855 }
18856
18857 void
18858 PushTail (int firstMove, int lastMove)
18859 {
18860         if(appData.icsActive) { // only in local mode
18861                 forwardMostMove = currentMove; // mimic old ICS behavior
18862                 return;
18863         }
18864         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18865
18866         PushInner(firstMove, lastMove);
18867         if(storedGames == 1) GreyRevert(FALSE);
18868         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18869 }
18870
18871 void
18872 PopInner (Boolean annotate)
18873 {
18874         int i, j, nrMoves;
18875         char buf[8000], moveBuf[20];
18876
18877         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18878         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18879         nrMoves = savedLast[storedGames] - currentMove;
18880         if(annotate) {
18881                 int cnt = 10;
18882                 if(!WhiteOnMove(currentMove))
18883                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18884                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18885                 for(i=currentMove; i<forwardMostMove; i++) {
18886                         if(WhiteOnMove(i))
18887                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18888                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18889                         strcat(buf, moveBuf);
18890                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18891                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18892                 }
18893                 strcat(buf, ")");
18894         }
18895         for(i=1; i<=nrMoves; i++) { // copy last variation back
18896             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18897             for(j=0; j<MOVE_LEN; j++)
18898                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18899             for(j=0; j<2*MOVE_LEN; j++)
18900                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18901             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18902             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18903             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18904             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18905             commentList[currentMove+i] = commentList[framePtr+i];
18906             commentList[framePtr+i] = NULL;
18907         }
18908         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18909         framePtr = savedFramePtr[storedGames];
18910         gameInfo.result = savedResult[storedGames];
18911         if(gameInfo.resultDetails != NULL) {
18912             free(gameInfo.resultDetails);
18913       }
18914         gameInfo.resultDetails = savedDetails[storedGames];
18915         forwardMostMove = currentMove + nrMoves;
18916 }
18917
18918 Boolean
18919 PopTail (Boolean annotate)
18920 {
18921         if(appData.icsActive) return FALSE; // only in local mode
18922         if(!storedGames) return FALSE; // sanity
18923         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18924
18925         PopInner(annotate);
18926         if(currentMove < forwardMostMove) ForwardEvent(); else
18927         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18928
18929         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18930         return TRUE;
18931 }
18932
18933 void
18934 CleanupTail ()
18935 {       // remove all shelved variations
18936         int i;
18937         for(i=0; i<storedGames; i++) {
18938             if(savedDetails[i])
18939                 free(savedDetails[i]);
18940             savedDetails[i] = NULL;
18941         }
18942         for(i=framePtr; i<MAX_MOVES; i++) {
18943                 if(commentList[i]) free(commentList[i]);
18944                 commentList[i] = NULL;
18945         }
18946         framePtr = MAX_MOVES-1;
18947         storedGames = 0;
18948 }
18949
18950 void
18951 LoadVariation (int index, char *text)
18952 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18953         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18954         int level = 0, move;
18955
18956         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18957         // first find outermost bracketing variation
18958         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18959             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18960                 if(*p == '{') wait = '}'; else
18961                 if(*p == '[') wait = ']'; else
18962                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18963                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18964             }
18965             if(*p == wait) wait = NULLCHAR; // closing ]} found
18966             p++;
18967         }
18968         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18969         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18970         end[1] = NULLCHAR; // clip off comment beyond variation
18971         ToNrEvent(currentMove-1);
18972         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18973         // kludge: use ParsePV() to append variation to game
18974         move = currentMove;
18975         ParsePV(start, TRUE, TRUE);
18976         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18977         ClearPremoveHighlights();
18978         CommentPopDown();
18979         ToNrEvent(currentMove+1);
18980 }
18981
18982 void
18983 LoadTheme ()
18984 {
18985     char *p, *q, buf[MSG_SIZ];
18986     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18987         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18988         ParseArgsFromString(buf);
18989         ActivateTheme(TRUE); // also redo colors
18990         return;
18991     }
18992     p = nickName;
18993     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18994     {
18995         int len;
18996         q = appData.themeNames;
18997         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18998       if(appData.useBitmaps) {
18999         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19000                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19001                 appData.liteBackTextureMode,
19002                 appData.darkBackTextureMode );
19003       } else {
19004         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
19005                 Col2Text(2),   // lightSquareColor
19006                 Col2Text(3) ); // darkSquareColor
19007       }
19008       if(appData.useBorder) {
19009         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19010                 appData.border);
19011       } else {
19012         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19013       }
19014       if(appData.useFont) {
19015         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19016                 appData.renderPiecesWithFont,
19017                 appData.fontToPieceTable,
19018                 Col2Text(9),    // appData.fontBackColorWhite
19019                 Col2Text(10) ); // appData.fontForeColorBlack
19020       } else {
19021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19022                 appData.pieceDirectory);
19023         if(!appData.pieceDirectory[0])
19024           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19025                 Col2Text(0),   // whitePieceColor
19026                 Col2Text(1) ); // blackPieceColor
19027       }
19028       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19029                 Col2Text(4),   // highlightSquareColor
19030                 Col2Text(5) ); // premoveHighlightColor
19031         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19032         if(insert != q) insert[-1] = NULLCHAR;
19033         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19034         if(q)   free(q);
19035     }
19036     ActivateTheme(FALSE);
19037 }